From 61e3e3ff67499a28abdf5799a8e8389ced783942 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 25 Oct 2026 11:57:38 +0100 Subject: [PATCH 1/2] [TypeDeclaration] Deprecate BinaryOpNullableToInstanceofRector, fold into ExplicitNullableObjectInstanceRector --- .../Fixture/binaryop_binary_or.php.inc | 33 +++++ .../binaryop_binary_or_negation.php.inc | 33 +++++ ...binary_or_negation_other_direction.php.inc | 33 +++++ .../Fixture/binaryop_both_negation.php.inc | 35 ++++++ .../Fixture/binaryop_both_truthy.php.inc | 35 ++++++ .../Fixture/binaryop_other_direction.php.inc | 35 ++++++ .../Fixture/binaryop_skip_assign.php.inc} | 4 +- .../Fixture/binaryop_skip_assign2.php.inc} | 4 +- .../Fixture/binaryop_skip_phpdoc.php.inc | 18 +++ .../Fixture/binaryop_some_class.php.inc | 33 +++++ .../Fixture/binaryop_with_property.php.inc | 43 +++++++ .../Source/SomeInstance.php | 13 ++ .../Source/SomeInstance2.php | 12 ++ ...BinaryOpNullableToInstanceofRectorTest.php | 28 ----- .../Fixture/binary_or.php.inc | 33 ----- .../Fixture/binary_or_negation.php.inc | 33 ----- ...binary_or_negation_other_direction.php.inc | 33 ----- .../Fixture/both_negation.php.inc | 35 ------ .../Fixture/both_truthy.php.inc | 35 ------ .../Fixture/other_direction.php.inc | 35 ------ .../Fixture/skip_phpdoc.php.inc | 18 --- .../Fixture/some_class.php.inc | 33 ----- .../Fixture/with_property.php.inc | 43 ------- .../Source/SomeInstance.php | 11 -- .../Source/SomeInstance2.php | 10 -- .../config/configured_rule.php | 9 -- .../ExplicitNullableObjectInstanceRector.php | 101 ++++++++++++++- .../BinaryOpNullableToInstanceofRector.php | 115 ++---------------- src/Config/Level/TypeDeclarationLevel.php | 4 - 29 files changed, 435 insertions(+), 472 deletions(-) create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_negation.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_truthy.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_other_direction.php.inc rename rules-tests/{TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_assign.php.inc => CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign.php.inc} (65%) rename rules-tests/{TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_assign2.php.inc => CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign2.php.inc} (66%) create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_phpdoc.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_some_class.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Source/SomeInstance.php create mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Source/SomeInstance2.php delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/BinaryOpNullableToInstanceofRectorTest.php delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation_other_direction.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_negation.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_truthy.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/other_direction.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_phpdoc.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/some_class.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/with_property.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Source/SomeInstance.php delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Source/SomeInstance2.php delete mode 100644 rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/config/configured_rule.php diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or.php.inc new file mode 100644 index 00000000000..8103f1433fa --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or.php.inc @@ -0,0 +1,33 @@ +someMethod()) { + return 'yes'; + } + + return 'no'; +} + +?> +----- +someMethod()) { + return 'yes'; + } + + return 'no'; +} + +?> diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc new file mode 100644 index 00000000000..ba6ef06798a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc @@ -0,0 +1,33 @@ +someMethod()) { + return 'yes'; + } + + return 'no'; +} + +?> +----- +someMethod()) { + return 'yes'; + } + + return 'no'; +} + +?> diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc new file mode 100644 index 00000000000..2c874b4fea7 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_negation.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_negation.php.inc new file mode 100644 index 00000000000..ae4db6b2325 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_negation.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_truthy.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_truthy.php.inc new file mode 100644 index 00000000000..d9365b9bddf --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_both_truthy.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_other_direction.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_other_direction.php.inc new file mode 100644 index 00000000000..a67029d3c7a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_other_direction.php.inc @@ -0,0 +1,35 @@ +someMethod(); + + } + + return 'no'; +} + +?> +----- +someMethod(); + + } + + return 'no'; +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_assign.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign.php.inc similarity index 65% rename from rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_assign.php.inc rename to rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign.php.inc index 4da84c219e9..3d9ad725f3c 100644 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_assign.php.inc +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign.php.inc @@ -1,8 +1,8 @@ someMethod()) { + return 'yes'; + } + + return 'no'; +} + diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_some_class.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_some_class.php.inc new file mode 100644 index 00000000000..3eebfb18184 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_some_class.php.inc @@ -0,0 +1,33 @@ +someMethod()) { + return 'yes'; + } + + return 'no'; +} + +?> +----- +someMethod()) { + return 'yes'; + } + + return 'no'; +} + +?> diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc new file mode 100644 index 00000000000..dbb213cffb7 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc @@ -0,0 +1,43 @@ +someClass && $this->someClass->someMethod()) { + return 'yes'; + } + + return 'no'; + } +} + +?> +----- +someClass instanceof \Rector\Tests\CodeQuality\Rector\If_\ExplicitNullableObjectInstanceRector\Source\SomeInstance && $this->someClass->someMethod()) { + return 'yes'; + } + + return 'no'; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Source/SomeInstance.php b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Source/SomeInstance.php new file mode 100644 index 00000000000..32ec759fcb7 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Source/SomeInstance.php @@ -0,0 +1,13 @@ +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/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or.php.inc deleted file mode 100644 index 8fa43bf1987..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or.php.inc +++ /dev/null @@ -1,33 +0,0 @@ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> ------ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation.php.inc deleted file mode 100644 index 60b41c67d31..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation.php.inc +++ /dev/null @@ -1,33 +0,0 @@ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> ------ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation_other_direction.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation_other_direction.php.inc deleted file mode 100644 index cee316ea525..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/binary_or_negation_other_direction.php.inc +++ /dev/null @@ -1,33 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_negation.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_negation.php.inc deleted file mode 100644 index 71b74672225..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_negation.php.inc +++ /dev/null @@ -1,35 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_truthy.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_truthy.php.inc deleted file mode 100644 index d1648ac1340..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/both_truthy.php.inc +++ /dev/null @@ -1,35 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/other_direction.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/other_direction.php.inc deleted file mode 100644 index 18b6e740efd..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/other_direction.php.inc +++ /dev/null @@ -1,35 +0,0 @@ -someMethod(); - - } - - return 'no'; -} - -?> ------ -someMethod(); - - } - - return 'no'; -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_phpdoc.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_phpdoc.php.inc deleted file mode 100644 index 12532d04f0d..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/skip_phpdoc.php.inc +++ /dev/null @@ -1,18 +0,0 @@ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/some_class.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/some_class.php.inc deleted file mode 100644 index 1a241128af3..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/some_class.php.inc +++ /dev/null @@ -1,33 +0,0 @@ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> ------ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/with_property.php.inc b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/with_property.php.inc deleted file mode 100644 index 42613fe54e7..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Fixture/with_property.php.inc +++ /dev/null @@ -1,43 +0,0 @@ -someClass && $this->someClass->someMethod()) { - return 'yes'; - } - - return 'no'; - } -} - -?> ------ -someClass instanceof \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance && $this->someClass->someMethod()) { - return 'yes'; - } - - return 'no'; - } -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Source/SomeInstance.php b/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Source/SomeInstance.php deleted file mode 100644 index 1735630d895..00000000000 --- a/rules-tests/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector/Source/SomeInstance.php +++ /dev/null @@ -1,11 +0,0 @@ -withRules([BinaryOpNullableToInstanceofRector::class]); diff --git a/rules/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector.php b/rules/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector.php index fa9d3303ece..8fcc828280f 100644 --- a/rules/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector.php +++ b/rules/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector.php @@ -8,11 +8,15 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\BinaryOp; +use PhpParser\Node\Expr\BinaryOp\BooleanAnd; +use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\BinaryOp\NotIdentical; use PhpParser\Node\Expr\BooleanNot; use PhpParser\Node\Expr\Empty_; use PhpParser\Node\Expr\Instanceof_; +use PhpParser\Node\Expr\Isset_; use PhpParser\Node\Expr\Ternary; use PhpParser\Node\Stmt\Do_; use PhpParser\Node\Stmt\ElseIf_; @@ -98,14 +102,20 @@ public function getNodeTypes(): array BooleanNot::class, While_::class, Do_::class, + BooleanAnd::class, + BooleanOr::class, ]; } /** - * @param If_|ElseIf_|Ternary|Identical|NotIdentical|Empty_|BooleanNot|While_|Do_ $node + * @param If_|ElseIf_|Ternary|Identical|NotIdentical|Empty_|BooleanNot|While_|Do_|BooleanAnd|BooleanOr $node */ public function refactor(Node $node): ?Node { + if ($node instanceof BooleanAnd || $node instanceof BooleanOr) { + return $this->refactorBinaryOp($node); + } + if ($node instanceof Identical || $node instanceof NotIdentical) { return $this->refactorNullCompare($node); } @@ -239,4 +249,93 @@ private function matchNullComparedExpr(Identical|NotIdentical $binaryOp): ?Expr return null; } + + private function refactorBinaryOp(BooleanAnd|BooleanOr $node): ?Node + { + if ($node->left instanceof Assign || $node->right instanceof Assign) { + return null; + } + + if ($node instanceof BooleanOr) { + return $this->processNegationBooleanOr($node); + } + + return $this->processNullableInstance($node); + } + + /** + * @template TBinaryOp of BooleanAnd|BooleanOr + * @param TBinaryOp $node + * @return TBinaryOp|null + */ + private function processNullableInstance(BooleanAnd|BooleanOr $node): null|BooleanAnd|BooleanOr + { + $hasChanged = false; + + $leftNullableObjectType = $this->resolveNullableObjectType($node->left); + if ($leftNullableObjectType instanceof ObjectType) { + $node->left = $this->nullableObjectToInstanceofFactory->create($node->left, $leftNullableObjectType, false); + $hasChanged = true; + } + + $rightNullableObjectType = $this->resolveNullableObjectType($node->right); + if ($rightNullableObjectType instanceof ObjectType) { + $node->right = $this->nullableObjectToInstanceofFactory->create($node->right, $rightNullableObjectType, false); + $hasChanged = true; + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + private function processNegationBooleanOr(BooleanOr $booleanOr): ?BooleanOr + { + $hasChanged = false; + if ($booleanOr->left instanceof BooleanNot) { + $nullableObjectType = $this->resolveNullableObjectType($booleanOr->left->expr); + + if ($nullableObjectType instanceof ObjectType) { + $booleanOr->left->expr = $this->nullableObjectToInstanceofFactory->create($booleanOr->left->expr, $nullableObjectType, false); + $hasChanged = true; + } + } + + if ($booleanOr->right instanceof BooleanNot) { + $nullableObjectType = $this->resolveNullableObjectType($booleanOr->right->expr); + + if ($nullableObjectType instanceof ObjectType) { + $booleanOr->right->expr = $this->nullableObjectToInstanceofFactory->create($booleanOr->right->expr, $nullableObjectType, false); + $hasChanged = true; + } + } + + if ($hasChanged) { + return $booleanOr; + } + + return $this->processNullableInstance($booleanOr); + } + + private function resolveNullableObjectType(Expr $expr): ?ObjectType + { + // cheap bail - bool/scalar-shaped operands can never be a nullable object, + // skip the costly native type resolution for them (e.g. chained "$a && $b && $c") + if ($this->isNeverNullableObject($expr)) { + return null; + } + + return $this->nullableTypeAnalyzer->resolveNullableObjectType($expr); + } + + private function isNeverNullableObject(Expr $expr): bool + { + return $expr instanceof BinaryOp + || $expr instanceof BooleanNot + || $expr instanceof Instanceof_ + || $expr instanceof Empty_ + || $expr instanceof Isset_; + } } diff --git a/rules/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector.php b/rules/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector.php index 11ae83b7c4d..51a7353b0fe 100644 --- a/rules/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector.php +++ b/rules/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector.php @@ -5,33 +5,19 @@ namespace Rector\TypeDeclaration\Rector\BooleanAnd; use PhpParser\Node; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\BinaryOp; use PhpParser\Node\Expr\BinaryOp\BooleanAnd; use PhpParser\Node\Expr\BinaryOp\BooleanOr; -use PhpParser\Node\Expr\BooleanNot; -use PhpParser\Node\Expr\Empty_; -use PhpParser\Node\Expr\Instanceof_; -use PhpParser\Node\Expr\Isset_; -use PHPStan\Type\ObjectType; -use Rector\PhpParser\Node\NullableObjectToInstanceofFactory; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; +use Rector\Exception\ShouldNotHappenException; use Rector\Rector\AbstractRector; use Rector\RuleDoc\CodeSample\CodeSample; use Rector\RuleDoc\RuleDefinition; -use Rector\TypeDeclaration\TypeAnalyzer\NullableTypeAnalyzer; /** - * @see \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\BinaryOpNullableToInstanceofRectorTest + * @deprecated This rule is deprecated. Use ExplicitNullableObjectInstanceRector instead, which handles nullable object checks across all condition idioms. */ -final class BinaryOpNullableToInstanceofRector extends AbstractRector +final class BinaryOpNullableToInstanceofRector extends AbstractRector implements DeprecatedInterface { - public function __construct( - private readonly NullableTypeAnalyzer $nullableTypeAnalyzer, - private readonly NullableObjectToInstanceofFactory $nullableObjectToInstanceofFactory, - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Change && and || between nullable objects to instanceof compares', [ @@ -75,94 +61,9 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($node->left instanceof Assign || $node->right instanceof Assign) { - return null; - } - - if ($node instanceof BooleanOr) { - return $this->processNegationBooleanOr($node); - } - - return $this->processNullableInstance($node); - } - - /** - * @template TBinaryOp of BooleanAnd|BooleanOr - * @param TBinaryOp $node - * @return TBinaryOp|null - */ - private function processNullableInstance(BooleanAnd|BooleanOr $node): null|BooleanAnd|BooleanOr - { - $nullableObjectType = $this->resolveNullableObjectType($node->left); - - $hasChanged = false; - if ($nullableObjectType instanceof ObjectType) { - $node->left = $this->nullableObjectToInstanceofFactory->create($node->left, $nullableObjectType, false); - - $hasChanged = true; - } - - $nullableObjectType = $this->resolveNullableObjectType($node->right); - - if ($nullableObjectType instanceof ObjectType) { - $node->right = $this->nullableObjectToInstanceofFactory->create($node->right, $nullableObjectType, false); - - $hasChanged = true; - } - - if ($hasChanged) { - return $node; - } - - return null; - } - - private function processNegationBooleanOr(BooleanOr $booleanOr): ?BooleanOr - { - $hasChanged = false; - if ($booleanOr->left instanceof BooleanNot) { - $nullableObjectType = $this->resolveNullableObjectType($booleanOr->left->expr); - - if ($nullableObjectType instanceof ObjectType) { - $booleanOr->left->expr = $this->nullableObjectToInstanceofFactory->create($booleanOr->left->expr, $nullableObjectType, false); - $hasChanged = true; - } - } - - if ($booleanOr->right instanceof BooleanNot) { - $nullableObjectType = $this->resolveNullableObjectType($booleanOr->right->expr); - - if ($nullableObjectType instanceof ObjectType) { - $booleanOr->right->expr = $this->nullableObjectToInstanceofFactory->create($booleanOr->right->expr, $nullableObjectType, false); - - $hasChanged = true; - } - } - - if ($hasChanged) { - return $booleanOr; - } - - return $this->processNullableInstance($booleanOr); - } - - private function resolveNullableObjectType(Expr $expr): ?ObjectType - { - // cheap bail - bool/scalar-shaped operands can never be a nullable object, - // skip the costly native type resolution for them (e.g. chained "$a && $b && $c") - if ($this->isNeverNullableObject($expr)) { - return null; - } - - return $this->nullableTypeAnalyzer->resolveNullableObjectType($expr); - } - - private function isNeverNullableObject(Expr $expr): bool - { - return $expr instanceof BinaryOp - || $expr instanceof BooleanNot - || $expr instanceof Instanceof_ - || $expr instanceof Empty_ - || $expr instanceof Isset_; + throw new ShouldNotHappenException(sprintf( + '"%s" rule is deprecated, use ExplicitNullableObjectInstanceRector instead', + self::class + )); } } diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index a4de4ffda4b..f4d3e7508ab 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -8,7 +8,6 @@ use Rector\Contract\Rector\RectorInterface; use Rector\Symfony\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector; use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector; -use Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector; use Rector\TypeDeclaration\Rector\Class_\AddTestsVoidReturnTypeWhereNoReturnRector; use Rector\TypeDeclaration\Rector\Class_\ChildDoctrineRepositoryClassTypeRector; use Rector\TypeDeclaration\Rector\Class_\MergeDateTimePropertyTypeDeclarationRector; @@ -120,9 +119,6 @@ final class TypeDeclarationLevel ReturnNullableTypeRector::class, - // php 7.4 - BinaryOpNullableToInstanceofRector::class, - // php 7.4 TypedPropertyFromStrictConstructorRector::class, AddParamTypeSplFixedArrayRector::class, From 2055ee55aea4c05d9ddde02c04eea55656d6e39e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 25 Oct 2026 12:00:25 +0100 Subject: [PATCH 2/2] Trim redundant binaryop fixtures, keep one per pattern --- .../binaryop_binary_or_negation.php.inc | 33 -------------- ...binary_or_negation_other_direction.php.inc | 33 -------------- .../Fixture/binaryop_skip_assign2.php.inc | 24 ----------- .../Fixture/binaryop_with_property.php.inc | 43 ------------------- 4 files changed, 133 deletions(-) delete mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign2.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc deleted file mode 100644 index ba6ef06798a..00000000000 --- a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation.php.inc +++ /dev/null @@ -1,33 +0,0 @@ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> ------ -someMethod()) { - return 'yes'; - } - - return 'no'; -} - -?> diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc deleted file mode 100644 index 2c874b4fea7..00000000000 --- a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_binary_or_negation_other_direction.php.inc +++ /dev/null @@ -1,33 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign2.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign2.php.inc deleted file mode 100644 index 619966480e3..00000000000 --- a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_skip_assign2.php.inc +++ /dev/null @@ -1,24 +0,0 @@ -someClass = $this->get()) && $this->someClass->someMethod()) { - return $this->someClass->someMethod(); - } - - return 'no'; - } - - private function get(): ?SomeInstance - { - return rand(0, 1) ? new SomeInstance() : null; - } -} diff --git a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc b/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc deleted file mode 100644 index dbb213cffb7..00000000000 --- a/rules-tests/CodeQuality/Rector/If_/ExplicitNullableObjectInstanceRector/Fixture/binaryop_with_property.php.inc +++ /dev/null @@ -1,43 +0,0 @@ -someClass && $this->someClass->someMethod()) { - return 'yes'; - } - - return 'no'; - } -} - -?> ------ -someClass instanceof \Rector\Tests\CodeQuality\Rector\If_\ExplicitNullableObjectInstanceRector\Source\SomeInstance && $this->someClass->someMethod()) { - return 'yes'; - } - - return 'no'; - } -} - -?>