From 5aa5296dafd83abb7a3156a43f01ceef1fa52984 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 6 Aug 2026 11:41:23 +0200 Subject: [PATCH 1/4] [Testing] Remove stray bare .php fixture leftover --- .../interface_implement.php | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/FixtureAddToInterfaceMethods/interface_implement.php diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/FixtureAddToInterfaceMethods/interface_implement.php b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/FixtureAddToInterfaceMethods/interface_implement.php deleted file mode 100644 index 1d2157809f8..00000000000 --- a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/FixtureAddToInterfaceMethods/interface_implement.php +++ /dev/null @@ -1,17 +0,0 @@ - From ba03075b8ca48704002b39455285626f87f4aa89 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 6 Aug 2026 11:38:28 +0200 Subject: [PATCH 2/4] [DeadCode] Add RemoveRedundantNullableTypeCheckRector --- .../nullable_int_from_method_call.php.inc | 47 ++++++ .../Fixture/nullable_string.php.inc | 35 ++++ .../Fixture/skip_another_variable.php.inc | 15 ++ .../Fixture/skip_untyped_param.php.inc | 15 ++ .../Fixture/skip_wider_union_type.php.inc | 15 ++ ...veRedundantNullableTypeCheckRectorTest.php | 28 ++++ .../config/configured_rule.php | 10 ++ ...RemoveRedundantNullableTypeCheckRector.php | 156 ++++++++++++++++++ src/Config/Level/DeadCodeLevel.php | 2 + 9 files changed, 323 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_string.php.inc create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_untyped_param.php.inc create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_wider_union_type.php.inc create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/RemoveRedundantNullableTypeCheckRectorTest.php create mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php create mode 100644 rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc new file mode 100644 index 00000000000..ab333364563 --- /dev/null +++ b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc @@ -0,0 +1,47 @@ +resolveCount(); + if ($count === null || ! is_int($count)) { + return; + } + + echo $count; + } + + private function resolveCount(): ?int + { + return 1; + } +} + +?> +----- +resolveCount(); + if ($count === null) { + return; + } + + echo $count; + } + + private function resolveCount(): ?int + { + return 1; + } +} + +?> diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_string.php.inc b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_string.php.inc new file mode 100644 index 00000000000..1e032184ba3 --- /dev/null +++ b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_string.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc new file mode 100644 index 00000000000..507b938a940 --- /dev/null +++ b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc @@ -0,0 +1,15 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php new file mode 100644 index 00000000000..745b3f748f0 --- /dev/null +++ b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RemoveRedundantNullableTypeCheckRector::class); +}; diff --git a/rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php b/rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php new file mode 100644 index 00000000000..e60007d0458 --- /dev/null +++ b/rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php @@ -0,0 +1,156 @@ +() check that can never fail next to null compare on a nullable value', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + public function run(?string $value) + { + if ($value === null || ! is_string($value)) { + return; + } + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeClass +{ + public function run(?string $value) + { + if ($value === null) { + return; + } + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [BooleanOr::class]; + } + + /** + * @param BooleanOr $node + */ + public function refactor(Node $node): ?Expr + { + if (! $node->left instanceof Identical) { + return null; + } + + $nullComparedExpr = $this->matchNullComparedExpr($node->left); + if (! $nullComparedExpr instanceof Variable) { + return null; + } + + if (! $node->right instanceof BooleanNot) { + return null; + } + + $funcCall = $node->right->expr; + if (! $funcCall instanceof FuncCall) { + return null; + } + + if ($funcCall->isFirstClassCallable()) { + return null; + } + + if (count($funcCall->getArgs()) !== 1) { + return null; + } + + if (! $this->nodeComparator->areNodesEqual($funcCall->getArgs()[0]->value, $nullComparedExpr)) { + return null; + } + + $funcCallName = $this->getName($funcCall); + if ($funcCallName === null) { + return null; + } + + $comparedType = $this->getType($nullComparedExpr); + if (! TypeCombinator::containsNull($comparedType)) { + return null; + } + + if (! $this->isAlwaysMatchingType($funcCallName, TypeCombinator::removeNull($comparedType))) { + return null; + } + + return $node->left; + } + + private function matchNullComparedExpr(Identical $identical): ?Expr + { + if ($this->valueResolver->isNull($identical->left)) { + return $identical->right; + } + + if ($this->valueResolver->isNull($identical->right)) { + return $identical->left; + } + + return null; + } + + private function isAlwaysMatchingType(string $funcCallName, Type $type): bool + { + return match ($funcCallName) { + 'is_string' => $type->isString() + ->yes(), + 'is_int', 'is_integer', 'is_long' => $type->isInteger() + ->yes(), + 'is_float', 'is_double' => $type->isFloat() + ->yes(), + 'is_bool' => $type->isBoolean() + ->yes(), + 'is_array' => $type->isArray() + ->yes(), + 'is_object' => $type->isObject() + ->yes(), + default => false, + }; + } +} diff --git a/src/Config/Level/DeadCodeLevel.php b/src/Config/Level/DeadCodeLevel.php index 81cded04455..2b8f43e1a99 100644 --- a/src/Config/Level/DeadCodeLevel.php +++ b/src/Config/Level/DeadCodeLevel.php @@ -16,6 +16,7 @@ use Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector; use Rector\DeadCode\Rector\Block\ReplaceBlockToItsStmtsRector; use Rector\DeadCode\Rector\BooleanAnd\RemoveAndTrueRector; +use Rector\DeadCode\Rector\BooleanOr\RemoveRedundantNullableTypeCheckRector; use Rector\DeadCode\Rector\Cast\RecastingRemovalRector; use Rector\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector; use Rector\DeadCode\Rector\ClassMethod\RemoveArgumentFromDefaultParentCallRector; @@ -148,6 +149,7 @@ final class DeadCodeLevel RemoveAlwaysTrueIfConditionRector::class, ReduceAlwaysFalseIfOrRector::class, + RemoveRedundantNullableTypeCheckRector::class, RemoveUnusedPrivateClassConstantRector::class, RemoveUnusedPrivatePropertyRector::class, RemoveUnusedClosureVariableUseRector::class, From 4b6120b8bbd2dc4ddf91e48bfc18b8b209faf65f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 6 Aug 2026 11:54:43 +0200 Subject: [PATCH 3/4] [DeadCode] Handle truthy && is_() in RemoveRedundantTypeCheckRector --- .../Fixture/array_param_truthy_check.php.inc | 31 +++++ .../nullable_int_from_method_call.php.inc | 4 +- .../Fixture/nullable_string.php.inc | 4 +- .../nullable_string_truthy_check.php.inc | 31 +++++ .../Fixture/skip_another_variable.php.inc | 2 +- .../skip_truthy_check_on_union.php.inc | 13 ++ .../Fixture/skip_untyped_param.php.inc | 2 +- .../Fixture/skip_wider_union_type.php.inc | 2 +- .../RemoveRedundantTypeCheckRectorTest.php} | 4 +- .../config/configured_rule.php | 10 ++ .../config/configured_rule.php | 10 -- .../RemoveRedundantTypeCheckRector.php} | 114 +++++++++++++----- src/Config/Level/DeadCodeLevel.php | 4 +- 13 files changed, 182 insertions(+), 49 deletions(-) create mode 100644 rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/array_param_truthy_check.php.inc rename rules-tests/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector => BinaryOp/RemoveRedundantTypeCheckRector}/Fixture/nullable_int_from_method_call.php.inc (75%) rename rules-tests/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector => BinaryOp/RemoveRedundantTypeCheckRector}/Fixture/nullable_string.php.inc (71%) create mode 100644 rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/nullable_string_truthy_check.php.inc rename rules-tests/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector => BinaryOp/RemoveRedundantTypeCheckRector}/Fixture/skip_another_variable.php.inc (70%) create mode 100644 rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_truthy_check_on_union.php.inc rename rules-tests/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector => BinaryOp/RemoveRedundantTypeCheckRector}/Fixture/skip_untyped_param.php.inc (67%) rename rules-tests/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector => BinaryOp/RemoveRedundantTypeCheckRector}/Fixture/skip_wider_union_type.php.inc (68%) rename rules-tests/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector/RemoveRedundantNullableTypeCheckRectorTest.php => BinaryOp/RemoveRedundantTypeCheckRector/RemoveRedundantTypeCheckRectorTest.php} (75%) create mode 100644 rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/config/configured_rule.php delete mode 100644 rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php rename rules/DeadCode/Rector/{BooleanOr/RemoveRedundantNullableTypeCheckRector.php => BinaryOp/RemoveRedundantTypeCheckRector.php} (50%) diff --git a/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/array_param_truthy_check.php.inc b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/array_param_truthy_check.php.inc new file mode 100644 index 00000000000..241ff58e2d6 --- /dev/null +++ b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/array_param_truthy_check.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc similarity index 75% rename from rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc rename to rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc index ab333364563..ebaa284cea5 100644 --- a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc +++ b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/nullable_int_from_method_call.php.inc @@ -1,6 +1,6 @@ +----- + diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_another_variable.php.inc similarity index 70% rename from rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc rename to rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_another_variable.php.inc index 507b938a940..fe67608e39a 100644 --- a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/Fixture/skip_another_variable.php.inc +++ b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_another_variable.php.inc @@ -1,6 +1,6 @@ rule(RemoveRedundantTypeCheckRector::class); +}; diff --git a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php deleted file mode 100644 index 745b3f748f0..00000000000 --- a/rules-tests/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector/config/configured_rule.php +++ /dev/null @@ -1,10 +0,0 @@ -rule(RemoveRedundantNullableTypeCheckRector::class); -}; diff --git a/rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php b/rules/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector.php similarity index 50% rename from rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php rename to rules/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector.php index e60007d0458..8ca3125856b 100644 --- a/rules/DeadCode/Rector/BooleanOr/RemoveRedundantNullableTypeCheckRector.php +++ b/rules/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace Rector\DeadCode\Rector\BooleanOr; +namespace Rector\DeadCode\Rector\BinaryOp; use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\BinaryOp\BooleanAnd; use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\BooleanNot; @@ -19,9 +20,9 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see \Rector\Tests\DeadCode\Rector\BooleanOr\RemoveRedundantNullableTypeCheckRector\RemoveRedundantNullableTypeCheckRectorTest + * @see \Rector\Tests\DeadCode\Rector\BinaryOp\RemoveRedundantTypeCheckRector\RemoveRedundantTypeCheckRectorTest */ -final class RemoveRedundantNullableTypeCheckRector extends AbstractRector +final class RemoveRedundantTypeCheckRector extends AbstractRector { public function __construct( private readonly ValueResolver $valueResolver @@ -30,36 +31,41 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition( - 'Remove is_() check that can never fail next to null compare on a nullable value', - [ - new CodeSample( - <<<'CODE_SAMPLE' + return new RuleDefinition('Remove is_() check that can never fail on already known type', [ + new CodeSample( + <<<'CODE_SAMPLE' class SomeClass { - public function run(?string $value) + public function run(?string $value, array $items) { if ($value === null || ! is_string($value)) { return; } + + if ($items && is_array($items)) { + return; + } } } CODE_SAMPLE - , - <<<'CODE_SAMPLE' + , + <<<'CODE_SAMPLE' class SomeClass { - public function run(?string $value) + public function run(?string $value, array $items) { if ($value === null) { return; } + + if ($items) { + return; + } } } CODE_SAMPLE - ), - ] - ); + ), + ]); } /** @@ -67,41 +73,72 @@ public function run(?string $value) */ public function getNodeTypes(): array { - return [BooleanOr::class]; + return [BooleanOr::class, BooleanAnd::class]; } /** - * @param BooleanOr $node + * @param BooleanOr|BooleanAnd $node */ public function refactor(Node $node): ?Expr { - if (! $node->left instanceof Identical) { + if ($node instanceof BooleanOr) { + return $this->refactorBooleanOr($node); + } + + return $this->refactorBooleanAnd($node); + } + + /** + * Handles "null === $value || ! is_string($value)", where is_string() can never fail once null is excluded + */ + private function refactorBooleanOr(BooleanOr $booleanOr): ?Expr + { + if (! $booleanOr->left instanceof Identical) { return null; } - $nullComparedExpr = $this->matchNullComparedExpr($node->left); + $nullComparedExpr = $this->matchNullComparedExpr($booleanOr->left); if (! $nullComparedExpr instanceof Variable) { return null; } - if (! $node->right instanceof BooleanNot) { + if (! $booleanOr->right instanceof BooleanNot) { return null; } - $funcCall = $node->right->expr; + $funcCall = $this->matchTypeCheckFuncCall($booleanOr->right->expr, $nullComparedExpr); if (! $funcCall instanceof FuncCall) { return null; } - if ($funcCall->isFirstClassCallable()) { + $funcCallName = $this->getName($funcCall); + if ($funcCallName === null) { return null; } - if (count($funcCall->getArgs()) !== 1) { + $comparedType = $this->getType($nullComparedExpr); + if (! TypeCombinator::containsNull($comparedType)) { return null; } - if (! $this->nodeComparator->areNodesEqual($funcCall->getArgs()[0]->value, $nullComparedExpr)) { + if (! $this->isAlwaysMatchingType($funcCallName, TypeCombinator::removeNull($comparedType))) { + return null; + } + + return $booleanOr->left; + } + + /** + * Handles "$items && is_array($items)", where is_array() can never fail on an array type + */ + private function refactorBooleanAnd(BooleanAnd $booleanAnd): ?Expr + { + if (! $booleanAnd->left instanceof Variable) { + return null; + } + + $funcCall = $this->matchTypeCheckFuncCall($booleanAnd->right, $booleanAnd->left); + if (! $funcCall instanceof FuncCall) { return null; } @@ -110,16 +147,37 @@ public function refactor(Node $node): ?Expr return null; } - $comparedType = $this->getType($nullComparedExpr); - if (! TypeCombinator::containsNull($comparedType)) { + // the type is already narrowed by the truthy check on the left + $checkedType = $this->getType($funcCall->getArgs()[0]->value); + if (! $this->isAlwaysMatchingType($funcCallName, $checkedType)) { return null; } - if (! $this->isAlwaysMatchingType($funcCallName, TypeCombinator::removeNull($comparedType))) { + return $booleanAnd->left; + } + + /** + * Matches "is_($expectedExpr)" single arg function call + */ + private function matchTypeCheckFuncCall(Expr $expr, Expr $expectedExpr): ?FuncCall + { + if (! $expr instanceof FuncCall) { + return null; + } + + if ($expr->isFirstClassCallable()) { + return null; + } + + if (count($expr->getArgs()) !== 1) { + return null; + } + + if (! $this->nodeComparator->areNodesEqual($expr->getArgs()[0]->value, $expectedExpr)) { return null; } - return $node->left; + return $expr; } private function matchNullComparedExpr(Identical $identical): ?Expr diff --git a/src/Config/Level/DeadCodeLevel.php b/src/Config/Level/DeadCodeLevel.php index 2b8f43e1a99..6a9a706dc04 100644 --- a/src/Config/Level/DeadCodeLevel.php +++ b/src/Config/Level/DeadCodeLevel.php @@ -14,9 +14,9 @@ use Rector\DeadCode\Rector\Assign\RemoveDoubleAssignRector; use Rector\DeadCode\Rector\Assign\RemoveDoubleSelfAssignRector; use Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector; +use Rector\DeadCode\Rector\BinaryOp\RemoveRedundantTypeCheckRector; use Rector\DeadCode\Rector\Block\ReplaceBlockToItsStmtsRector; use Rector\DeadCode\Rector\BooleanAnd\RemoveAndTrueRector; -use Rector\DeadCode\Rector\BooleanOr\RemoveRedundantNullableTypeCheckRector; use Rector\DeadCode\Rector\Cast\RecastingRemovalRector; use Rector\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector; use Rector\DeadCode\Rector\ClassMethod\RemoveArgumentFromDefaultParentCallRector; @@ -149,7 +149,7 @@ final class DeadCodeLevel RemoveAlwaysTrueIfConditionRector::class, ReduceAlwaysFalseIfOrRector::class, - RemoveRedundantNullableTypeCheckRector::class, + RemoveRedundantTypeCheckRector::class, RemoveUnusedPrivateClassConstantRector::class, RemoveUnusedPrivatePropertyRector::class, RemoveUnusedClosureVariableUseRector::class, From cc10d1dfda45450f1e0d6bb3cbdf1157c7d0b257 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 6 Aug 2026 16:12:27 +0200 Subject: [PATCH 4/4] use native type and skip docblock-only param types --- .../Fixture/skip_docblock_array_param.php.inc | 16 ++++++++++++++++ .../skip_docblock_nullable_param.php.inc | 18 ++++++++++++++++++ .../RemoveRedundantTypeCheckRector.php | 18 +++++++++++++++--- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_docblock_array_param.php.inc create mode 100644 rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_docblock_nullable_param.php.inc diff --git a/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_docblock_array_param.php.inc b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_docblock_array_param.php.inc new file mode 100644 index 00000000000..809ea08b494 --- /dev/null +++ b/rules-tests/DeadCode/Rector/BinaryOp/RemoveRedundantTypeCheckRector/Fixture/skip_docblock_array_param.php.inc @@ -0,0 +1,16 @@ +exprAnalyzer->isNonTypedFromParam($nullComparedExpr)) { + return null; + } + if (! $booleanOr->right instanceof BooleanNot) { return null; } @@ -116,7 +123,7 @@ private function refactorBooleanOr(BooleanOr $booleanOr): ?Expr return null; } - $comparedType = $this->getType($nullComparedExpr); + $comparedType = $this->getNativeType($nullComparedExpr); if (! TypeCombinator::containsNull($comparedType)) { return null; } @@ -137,6 +144,11 @@ private function refactorBooleanAnd(BooleanAnd $booleanAnd): ?Expr return null; } + // the docblock type can be wider than the real value + if ($this->exprAnalyzer->isNonTypedFromParam($booleanAnd->left)) { + return null; + } + $funcCall = $this->matchTypeCheckFuncCall($booleanAnd->right, $booleanAnd->left); if (! $funcCall instanceof FuncCall) { return null; @@ -148,7 +160,7 @@ private function refactorBooleanAnd(BooleanAnd $booleanAnd): ?Expr } // the type is already narrowed by the truthy check on the left - $checkedType = $this->getType($funcCall->getArgs()[0]->value); + $checkedType = $this->getNativeType($funcCall->getArgs()[0]->value); if (! $this->isAlwaysMatchingType($funcCallName, $checkedType)) { return null; }