From 13c02b805eebb869c759e790d9cbbb3e36276a21 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:00:28 +0700 Subject: [PATCH 01/29] [DowngradePhp80] Add DowngradeSubstrFalsyRector --- config/set/downgrade-php80.php | 2 + .../DowngradeSubstrFalsyRectorTest.php | 28 +++++++ .../Fixture/by_variable_string.php.inc | 27 +++++++ .../Fixture/fixture.php.inc | 27 +++++++ .../Fixture/skip_already_casted.php.inc | 11 +++ .../Fixture/skip_different_func_call.php.inc | 11 +++ .../Fixture/skip_empty.php.inc | 14 ++++ .../Fixture/skip_never_empty.php.inc | 11 +++ .../Fixture/skip_ternary_falsy.php.inc | 11 +++ .../config/configured_rule.php | 10 +++ .../FuncCall/DowngradeSubstrFalsyRector.php | 74 +++++++++++++++++++ 11 files changed, 226 insertions(+) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/DowngradeSubstrFalsyRectorTest.php create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/by_variable_string.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/fixture.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_already_casted.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_different_func_call.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_empty.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_never_empty.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_ternary_falsy.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/config/configured_rule.php create mode 100644 rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php diff --git a/config/set/downgrade-php80.php b/config/set/downgrade-php80.php index ce89592e..9e13c73c 100644 --- a/config/set/downgrade-php80.php +++ b/config/set/downgrade-php80.php @@ -23,6 +23,7 @@ use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrContainsRector; use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector; use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector; +use Rector\DowngradePhp80\Rector\FuncCall\DowngradeSubstrFalsyRector; use Rector\DowngradePhp80\Rector\FunctionLike\DowngradeMixedTypeDeclarationRector; use Rector\DowngradePhp80\Rector\FunctionLike\DowngradeUnionTypeDeclarationRector; use Rector\DowngradePhp80\Rector\Instanceof_\DowngradeInstanceofStringableRector; @@ -91,5 +92,6 @@ RemoveReturnTypeDeclarationFromCloneRector::class, DowngradeEnumToConstantListClassRector::class, DowngradeInstanceofStringableRector::class, + DowngradeSubstrFalsyRector::class, ]); }; diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/DowngradeSubstrFalsyRectorTest.php b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/DowngradeSubstrFalsyRectorTest.php new file mode 100644 index 00000000..e7f4d292 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/DowngradeSubstrFalsyRectorTest.php @@ -0,0 +1,28 @@ +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/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/by_variable_string.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/by_variable_string.php.inc new file mode 100644 index 00000000..464b3fc6 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/by_variable_string.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/fixture.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/fixture.php.inc new file mode 100644 index 00000000..ea7b6685 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/fixture.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_already_casted.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_already_casted.php.inc new file mode 100644 index 00000000..bd271390 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_already_casted.php.inc @@ -0,0 +1,11 @@ +rule(DowngradeSubstrFalsyRector::class); +}; diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php new file mode 100644 index 00000000..0487b3bd --- /dev/null +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -0,0 +1,74 @@ +> + */ + public function getNodeTypes(): array + { + return [String_::class, Empty_::class, Ternary::class, FuncCall::class]; + } + + /** + * @param String_|Empty_|Ternary|FuncCall $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof String_ || $node instanceof Empty_) { + $node->expr->setAttribute(self::IS_UNCASTABLE, true); + return null; + } + + if ($node instanceof Ternary && ! $node->if instanceof Expr) { + $node->cond->setAttribute(self::IS_UNCASTABLE, true); + return null; + } + + if (! $this->isName($node, 'substr')) { + return null; + } + + if ($node->getAttribute(self::IS_UNCASTABLE) === true) { + return null; + } + + $type = $this->getType($node); + if ($type->isNonEmptyString()->yes()) { + return null; + } + + return new String_($node); + } +} From 6d51910793e911fe742d62b839eb95fa29d06ed7 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:02:16 +0700 Subject: [PATCH 02/29] [DowngradePhp80] Add DowngradeSubstrFalsyRector --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index 0487b3bd..0882f9c8 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -51,8 +51,11 @@ public function refactor(Node $node): ?Node return null; } - if ($node instanceof Ternary && ! $node->if instanceof Expr) { - $node->cond->setAttribute(self::IS_UNCASTABLE, true); + if ($node instanceof Ternary) { + if ($node->if instanceof Expr) { + $node->cond->setAttribute(self::IS_UNCASTABLE, true); + } + return null; } From 12d80c8575e84a12400b6787f63dae1e6186bede Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:02:55 +0700 Subject: [PATCH 03/29] [DowngradePhp80] Add DowngradeSubstrFalsyRector --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index 0882f9c8..8559df87 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -52,7 +52,7 @@ public function refactor(Node $node): ?Node } if ($node instanceof Ternary) { - if ($node->if instanceof Expr) { + if (! $node->if instanceof Expr) { $node->cond->setAttribute(self::IS_UNCASTABLE, true); } From edb3a4f488885fcd93105d62d12edaa55e27fd2e Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:04:21 +0700 Subject: [PATCH 04/29] [DowngradePhp80] Add DowngradeSubstrFalsyRector --- tests/Issues/DowngradeHeredoc/Fixture/next_arg.php.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Issues/DowngradeHeredoc/Fixture/next_arg.php.inc b/tests/Issues/DowngradeHeredoc/Fixture/next_arg.php.inc index 1f117df8..db204c12 100644 --- a/tests/Issues/DowngradeHeredoc/Fixture/next_arg.php.inc +++ b/tests/Issues/DowngradeHeredoc/Fixture/next_arg.php.inc @@ -474,9 +474,9 @@ final class NextArg $lineString = trim($lineString); if (strncmp($lineString, '/**', strlen('/**')) === 0) { - $lineString = substr($lineString, 3); + $lineString = (string) substr($lineString, 3); } elseif (strncmp($lineString, '*', strlen('*')) === 0) { - $lineString = substr($lineString, 1); + $lineString = (string) substr($lineString, 1); } return trim($lineString); From d9635109bc95ef4296083a7d205523939f9e749d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:11:44 +0700 Subject: [PATCH 05/29] skip negated --- .../Fixture/skip_negated.php.inc | 14 ++++++++++++++ .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 7 ++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negated.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negated.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negated.php.inc new file mode 100644 index 00000000..ed83c522 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negated.php.inc @@ -0,0 +1,14 @@ +expr->setAttribute(self::IS_UNCASTABLE, true); return null; } From e5b2a2ee753437ab3aba3830d649686b699f70c0 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:14:16 +0700 Subject: [PATCH 06/29] skip casted bool --- .../Fixture/skip_casted_bool.php.inc | 11 +++++++++++ .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_casted_bool.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_casted_bool.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_casted_bool.php.inc new file mode 100644 index 00000000..f78c7eeb --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_casted_bool.php.inc @@ -0,0 +1,11 @@ +expr->setAttribute(self::IS_UNCASTABLE, true); return null; } From 121104e0fe36a3899fedf4aa3fc399a1af49f65c Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:25:00 +0700 Subject: [PATCH 07/29] skip compared to false --- .../Fixture/skip_compared_to_false.php.inc | 12 +++++++++ .../FuncCall/DowngradeSubstrFalsyRector.php | 25 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc new file mode 100644 index 00000000..8d51b460 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc @@ -0,0 +1,12 @@ +valueResolver->isFalse($node->left)) { + $node->right->setAttribute(self::IS_UNCASTABLE, true); + } + + if ($this->valueResolver->isFalse($node->right)) { + $node->left->setAttribute(self::IS_UNCASTABLE, true); + } + + return null; + } + if (! $this->isName($node, 'substr')) { return null; } From 15357353e65f3d2697b3353abedadfdeeedca2e7 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 13 Oct 2025 14:25:49 +0000 Subject: [PATCH 08/29] [ci-review] Rector Rectify --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index 528b418e..7bf78ed1 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -49,7 +49,15 @@ public function getRuleDefinition(): RuleDefinition */ public function getNodeTypes(): array { - return [Cast::class, Empty_::class, BooleanNot::class, Ternary::class, Equal::class, Identical::class, FuncCall::class]; + return [ + Cast::class, + Empty_::class, + BooleanNot::class, + Ternary::class, + Equal::class, + Identical::class, + FuncCall::class, + ]; } /** From d653bba71a3c81766e589dc3a94ccf4595ef1335 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:26:15 +0700 Subject: [PATCH 09/29] skip compared to false --- .../Fixture/skip_compared_to_false.php.inc | 1 - .../FuncCall/DowngradeSubstrFalsyRector.php | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc index 8d51b460..619faa6a 100644 --- a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_compared_to_false.php.inc @@ -6,7 +6,6 @@ class SkipComparedToFalse { public function run(string $name) { - substr($name, 2) == false; substr($name, 2) === false; } } diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index 7bf78ed1..5829e14c 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -6,7 +6,6 @@ use PhpParser\Node; use PhpParser\Node\Expr; -use PhpParser\Node\Expr\BinaryOp\Equal; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\BooleanNot; use PhpParser\Node\Expr\Cast; @@ -49,19 +48,11 @@ public function getRuleDefinition(): RuleDefinition */ public function getNodeTypes(): array { - return [ - Cast::class, - Empty_::class, - BooleanNot::class, - Ternary::class, - Equal::class, - Identical::class, - FuncCall::class, - ]; + return [Cast::class, Empty_::class, BooleanNot::class, Ternary::class, Identical::class, FuncCall::class]; } /** - * @param Cast|Empty_|BooleanNot|Ternary|Equal|Identical|FuncCall $node + * @param Cast|Empty_|BooleanNot|Ternary|Identical|FuncCall $node */ public function refactor(Node $node): ?Node { @@ -78,7 +69,7 @@ public function refactor(Node $node): ?Node return null; } - if ($node instanceof Equal || $node instanceof Identical) { + if ($node instanceof Identical) { if ($this->valueResolver->isFalse($node->left)) { $node->right->setAttribute(self::IS_UNCASTABLE, true); } From e0d8b56bdb7808e29e597e46687db71c8cf0eb1a Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:45:03 +0700 Subject: [PATCH 10/29] skip zero offset --- .../skip_zero_offset_with_no_length.php.inc | 14 ++++++++++++++ ...p_zero_offset_with_positive_length.php.inc | 14 ++++++++++++++ .../FuncCall/DowngradeSubstrFalsyRector.php | 19 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_zero_offset_with_no_length.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_zero_offset_with_positive_length.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_zero_offset_with_no_length.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_zero_offset_with_no_length.php.inc new file mode 100644 index 00000000..f05440b4 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_zero_offset_with_no_length.php.inc @@ -0,0 +1,14 @@ +getArg('offset', 1); + + if ($offset instanceof Arg) { + $offsetType = $this->getType($offset->value); + if ($offsetType instanceof ConstantIntegerType && $offsetType->getValue() === 0) { + return null; + } + + $length = $node->getArg('length', 2); + if ($length instanceof Arg) { + $lengthType = $this->getType($length->value); + if ($lengthType instanceof ConstantIntegerType && $lengthType->getValue() >= 0) { + return null; + } + } + } + return new String_($node); } } From 4b37a51f674a921ed7cced1340681bdb298f9942 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:45:29 +0700 Subject: [PATCH 11/29] skip zero offset --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index e6c63183..249d051d 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -100,7 +100,7 @@ public function refactor(Node $node): ?Node if ($offset instanceof Arg) { $offsetType = $this->getType($offset->value); - if ($offsetType instanceof ConstantIntegerType && $offsetType->getValue() === 0) { + if ($offsetType instanceof ConstantIntegerType && $offsetType->getValue() <= 0) { return null; } From c5feda89f60c45402c1f9251e0b6ac3da014d701 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 21:48:55 +0700 Subject: [PATCH 12/29] skip negative offset --- .../Fixture/skip_negative_offset.php.inc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negative_offset.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negative_offset.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negative_offset.php.inc new file mode 100644 index 00000000..9a942831 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_negative_offset.php.inc @@ -0,0 +1,14 @@ + Date: Mon, 13 Oct 2025 22:08:16 +0700 Subject: [PATCH 13/29] skip with concat --- .../Fixture/skip_with_concat.php.inc.inc | 14 ++++++++++++++ .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 11 +++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_with_concat.php.inc.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_with_concat.php.inc.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_with_concat.php.inc.inc new file mode 100644 index 00000000..b7aa24db --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_with_concat.php.inc.inc @@ -0,0 +1,14 @@ +left->setAttribute(self::IS_UNCASTABLE, true); + $node->right->setAttribute(self::IS_UNCASTABLE, true); + return null; + } + if ($node instanceof Identical) { if ($this->valueResolver->isFalse($node->left)) { $node->right->setAttribute(self::IS_UNCASTABLE, true); From 0569d22c98d646a11607970d0e7f413152b02caa Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 13 Oct 2025 15:09:09 +0000 Subject: [PATCH 14/29] [ci-review] Rector Rectify --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index b8d6d6e0..21ac0a92 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -51,7 +51,15 @@ public function getRuleDefinition(): RuleDefinition */ public function getNodeTypes(): array { - return [Cast::class, Empty_::class, BooleanNot::class, Ternary::class, Identical::class, Concat::class, FuncCall::class]; + return [ + Cast::class, + Empty_::class, + BooleanNot::class, + Ternary::class, + Identical::class, + Concat::class, + FuncCall::class, + ]; } /** From a3efa295a9b80a503eb54e38b074b41ad68bec95 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 22:23:07 +0700 Subject: [PATCH 15/29] skip allow falsy arg --- .../Fixture/skip_as_falsy_arg.php.inc | 16 ++++++++ .../FuncCall/DowngradeSubstrFalsyRector.php | 39 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_falsy_arg.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_falsy_arg.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_falsy_arg.php.inc new file mode 100644 index 00000000..50c8efb3 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_falsy_arg.php.inc @@ -0,0 +1,16 @@ +execute(substr('a', 2)); + } + + private function execute(false|string $value) + { + return $value; + } +} diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index 21ac0a92..c658ec15 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -10,14 +10,20 @@ use PhpParser\Node\Expr\BinaryOp\Concat; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\BooleanNot; +use PhpParser\Node\Expr\CallLike; use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\Cast\String_; use PhpParser\Node\Expr\Empty_; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Ternary; +use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantIntegerType; +use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper; use Rector\PhpParser\Node\Value\ValueResolver; +use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; +use Rector\Reflection\ReflectionResolver; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -34,6 +40,7 @@ final class DowngradeSubstrFalsyRector extends AbstractRector private const IS_UNCASTABLE = 'is_uncastable'; public function __construct( + private readonly ReflectionResolver $reflectionResolver, private readonly ValueResolver $valueResolver ) { @@ -58,12 +65,13 @@ public function getNodeTypes(): array Ternary::class, Identical::class, Concat::class, + CallLike::class, FuncCall::class, ]; } /** - * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|FuncCall $node + * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|CallLike|FuncCall $node */ public function refactor(Node $node): ?Node { @@ -98,6 +106,35 @@ public function refactor(Node $node): ?Node return null; } + if ($node instanceof CallLike) { + if ($node->isFirstClassCallable()) { + return null; + } + + $reflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); + + if (! $reflection instanceof MethodReflection && ! $reflection instanceof FunctionReflection) { + return null; + } + + $parameterAcceptor = ParametersAcceptorSelectorVariantsWrapper::select( + $reflection, + $node, + ScopeFetcher::fetch($node) + ); + + foreach ($parameterAcceptor->getParameters() as $position => $parameterReflection) { + if ($parameterReflection->getType()->isFalse()->no()) { + continue; + } + + $arg = $node->getArg($parameterReflection->getName(), $position); + if ($arg instanceof Arg) { + $arg->value->setAttribute(self::IS_UNCASTABLE, true); + } + } + } + if (! $this->isName($node, 'substr')) { return null; } From 12077fb9ca7128db02aa36bd72ef679a44d2870c Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 13 Oct 2025 22:28:53 +0700 Subject: [PATCH 16/29] use MethodCall and StaticCall as current ReflectionResolver can get --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index c658ec15..53122027 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -15,6 +15,8 @@ use PhpParser\Node\Expr\Cast\String_; use PhpParser\Node\Expr\Empty_; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Ternary; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; @@ -65,13 +67,14 @@ public function getNodeTypes(): array Ternary::class, Identical::class, Concat::class, - CallLike::class, + MethodCall::class, + StaticCall::class, FuncCall::class, ]; } /** - * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|CallLike|FuncCall $node + * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|MethodCall|StaticCall|FuncCall $node */ public function refactor(Node $node): ?Node { From 6abd0758e8bd2ad8a24ab8f66644c4c7aaa00ded Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 14 Oct 2025 06:54:26 +0700 Subject: [PATCH 17/29] skip append --- .../Fixture/skip_append.php.inc | 13 +++++++++++++ .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_append.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_append.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_append.php.inc new file mode 100644 index 00000000..bdde85fa --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_append.php.inc @@ -0,0 +1,13 @@ +expr->setAttribute(self::IS_UNCASTABLE, true); return null; } From b76a519d463d57c7e8678185e06444e50d4e25f1 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 15 Oct 2025 16:45:13 +0700 Subject: [PATCH 18/29] skip direct on if cond --- .../Fixture/compared_to_empty_string.php.inc | 47 +++++++++++++++++++ .../Fixture/skip_direct_cond.php.inc | 13 +++++ .../FuncCall/DowngradeSubstrFalsyRector.php | 9 +++- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_direct_cond.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc new file mode 100644 index 00000000..4bb99d19 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc @@ -0,0 +1,47 @@ + +----- + diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_direct_cond.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_direct_cond.php.inc new file mode 100644 index 00000000..a1998c9f --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_direct_cond.php.inc @@ -0,0 +1,13 @@ +cond->setAttribute(self::IS_UNCASTABLE, true); + return null; + } + if ($node instanceof CallLike) { if ($node->isFirstClassCallable()) { return null; From 0c65912856af1206ecb55107f7744d153b24c946 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 15 Oct 2025 16:45:55 +0700 Subject: [PATCH 19/29] skip direct on if cond --- .../Fixture/compared_to_empty_string.php.inc | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc deleted file mode 100644 index 4bb99d19..00000000 --- a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/compared_to_empty_string.php.inc +++ /dev/null @@ -1,47 +0,0 @@ - ------ - From 1ca7c16694180a0d2fa69deb1ad29858afcc8444 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 15 Oct 2025 16:47:00 +0700 Subject: [PATCH 20/29] skip direct on while and do cond --- .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index 27f85033..ca547610 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -19,7 +19,9 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Ternary; +use PhpParser\Node\Stmt\Do_; use PhpParser\Node\Stmt\If_; +use PhpParser\Node\Stmt\While_; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantIntegerType; @@ -73,12 +75,14 @@ public function getNodeTypes(): array StaticCall::class, AssignOp::class, If_::class, + While_::class, + Do_::class, FuncCall::class, ]; } /** - * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|MethodCall|StaticCall|AssignOp|If_|FuncCall $node + * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|MethodCall|StaticCall|AssignOp|If_|While_|Do_|FuncCall $node */ public function refactor(Node $node): ?Node { @@ -113,7 +117,7 @@ public function refactor(Node $node): ?Node return null; } - if ($node instanceof If_) { + if ($node instanceof If_ || $node instanceof While_ || $node instanceof Do_) { $node->cond->setAttribute(self::IS_UNCASTABLE, true); return null; } From 47562f06ed9a7d4e7e0800b42c3844a6ea0ac0b4 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 15 Oct 2025 17:25:44 +0700 Subject: [PATCH 21/29] add fixture for skip sprintf arg --- .../Fixture/skip_sprintf.php.inc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_sprintf.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_sprintf.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_sprintf.php.inc new file mode 100644 index 00000000..851758d6 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_sprintf.php.inc @@ -0,0 +1,18 @@ + Date: Fri, 17 Oct 2025 11:17:30 +0700 Subject: [PATCH 22/29] skip as array key --- .../Fixture/skip_as_array_key.php.inc | 15 +++++++++++++++ .../FuncCall/DowngradeSubstrFalsyRector.php | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_key.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_key.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_key.php.inc new file mode 100644 index 00000000..2e98fcdd --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_key.php.inc @@ -0,0 +1,15 @@ + 'foo' + ]; + + var_dump($data); + } +} \ No newline at end of file diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index ca547610..d1933af8 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Arg; +use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr; use PhpParser\Node\Expr\AssignOp; use PhpParser\Node\Expr\BinaryOp\Concat; @@ -77,12 +78,13 @@ public function getNodeTypes(): array If_::class, While_::class, Do_::class, + ArrayItem::class, FuncCall::class, ]; } /** - * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|MethodCall|StaticCall|AssignOp|If_|While_|Do_|FuncCall $node + * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|MethodCall|StaticCall|AssignOp|If_|While_|Do_|ArrayItem|FuncCall $node */ public function refactor(Node $node): ?Node { @@ -122,6 +124,14 @@ public function refactor(Node $node): ?Node return null; } + if ($node instanceof ArrayItem) { + if ($node->key instanceof Expr) { + $node->key->setAttribute(self::IS_UNCASTABLE, true); + } + + return null; + } + if ($node instanceof CallLike) { if ($node->isFirstClassCallable()) { return null; From 7e86548f24186ff66bb645e7cd5728d5726ce15a Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 17 Oct 2025 11:19:45 +0700 Subject: [PATCH 23/29] skip as array dim fetch dim --- .../Fixture/skip_as_array_dim_fetch_dim.php.inc | 12 ++++++++++++ .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 12 +++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_dim_fetch_dim.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_dim_fetch_dim.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_dim_fetch_dim.php.inc new file mode 100644 index 00000000..7e2d68fa --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_as_array_dim_fetch_dim.php.inc @@ -0,0 +1,12 @@ +dim instanceof Expr) { + $node->dim->setAttribute(self::IS_UNCASTABLE, true); + } + + return null; + } + if ($node instanceof CallLike) { if ($node->isFirstClassCallable()) { return null; From 0acd09fdfc42e2fbf92e7b81b1c738401151b7a0 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 17 Oct 2025 12:17:48 +0700 Subject: [PATCH 24/29] skip in binary op --- .../Fixture/skip_in_binary_op.php.inc | 11 +++++++++++ .../Rector/FuncCall/DowngradeSubstrFalsyRector.php | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_in_binary_op.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_in_binary_op.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_in_binary_op.php.inc new file mode 100644 index 00000000..73392e4a --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_in_binary_op.php.inc @@ -0,0 +1,11 @@ +left->setAttribute(self::IS_UNCASTABLE, true); + $node->right->setAttribute(self::IS_UNCASTABLE, true); + return null; + } + if ($node instanceof CallLike) { if ($node->isFirstClassCallable()) { return null; From 1272b811bd45e4193e2f2fc330601f3a65cd2bd4 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 17 Oct 2025 13:23:48 +0700 Subject: [PATCH 25/29] skip new arg falsy --- .../Fixture/skip_new_arg_falsy.php.inc | 15 +++++++++++++++ .../FuncCall/DowngradeSubstrFalsyRector.php | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_new_arg_falsy.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_new_arg_falsy.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_new_arg_falsy.php.inc new file mode 100644 index 00000000..d464729d --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/skip_new_arg_falsy.php.inc @@ -0,0 +1,15 @@ + Date: Mon, 20 Oct 2025 16:58:38 +0700 Subject: [PATCH 26/29] update constant --- .../FuncCall/DowngradeSubstrFalsyRector.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index a8644745..f83b2c19 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -47,7 +47,7 @@ final class DowngradeSubstrFalsyRector extends AbstractRector /** * @var string */ - private const IS_UNCASTABLE = 'is_uncastable'; + private const IS_FALSY_UNCASTABLE = 'is_falsy_uncastable'; public function __construct( private readonly ReflectionResolver $reflectionResolver, @@ -95,44 +95,44 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { if ($node instanceof Cast || $node instanceof Empty_ || $node instanceof BooleanNot || $node instanceof AssignOp) { - $node->expr->setAttribute(self::IS_UNCASTABLE, true); + $node->expr->setAttribute(self::IS_FALSY_UNCASTABLE, true); return null; } if ($node instanceof Ternary) { if (! $node->if instanceof Expr) { - $node->cond->setAttribute(self::IS_UNCASTABLE, true); + $node->cond->setAttribute(self::IS_FALSY_UNCASTABLE, true); } return null; } if ($node instanceof Concat) { - $node->left->setAttribute(self::IS_UNCASTABLE, true); - $node->right->setAttribute(self::IS_UNCASTABLE, true); + $node->left->setAttribute(self::IS_FALSY_UNCASTABLE, true); + $node->right->setAttribute(self::IS_FALSY_UNCASTABLE, true); return null; } if ($node instanceof Identical) { if ($this->valueResolver->isFalse($node->left)) { - $node->right->setAttribute(self::IS_UNCASTABLE, true); + $node->right->setAttribute(self::IS_FALSY_UNCASTABLE, true); } if ($this->valueResolver->isFalse($node->right)) { - $node->left->setAttribute(self::IS_UNCASTABLE, true); + $node->left->setAttribute(self::IS_FALSY_UNCASTABLE, true); } return null; } if ($node instanceof If_ || $node instanceof While_ || $node instanceof Do_) { - $node->cond->setAttribute(self::IS_UNCASTABLE, true); + $node->cond->setAttribute(self::IS_FALSY_UNCASTABLE, true); return null; } if ($node instanceof ArrayItem) { if ($node->key instanceof Expr) { - $node->key->setAttribute(self::IS_UNCASTABLE, true); + $node->key->setAttribute(self::IS_FALSY_UNCASTABLE, true); } return null; @@ -140,15 +140,15 @@ public function refactor(Node $node): ?Node if ($node instanceof ArrayDimFetch) { if ($node->dim instanceof Expr) { - $node->dim->setAttribute(self::IS_UNCASTABLE, true); + $node->dim->setAttribute(self::IS_FALSY_UNCASTABLE, true); } return null; } if ($node instanceof BinaryOp) { - $node->left->setAttribute(self::IS_UNCASTABLE, true); - $node->right->setAttribute(self::IS_UNCASTABLE, true); + $node->left->setAttribute(self::IS_FALSY_UNCASTABLE, true); + $node->right->setAttribute(self::IS_FALSY_UNCASTABLE, true); return null; } @@ -176,7 +176,7 @@ public function refactor(Node $node): ?Node $arg = $node->getArg($parameterReflection->getName(), $position); if ($arg instanceof Arg) { - $arg->value->setAttribute(self::IS_UNCASTABLE, true); + $arg->value->setAttribute(self::IS_FALSY_UNCASTABLE, true); } } } @@ -185,7 +185,7 @@ public function refactor(Node $node): ?Node return null; } - if ($node->getAttribute(self::IS_UNCASTABLE) === true) { + if ($node->getAttribute(self::IS_FALSY_UNCASTABLE) === true) { return null; } From 7180a0a4e1f18b739359edccda7f402c2c6f1920 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 20 Oct 2025 17:05:01 +0700 Subject: [PATCH 27/29] add failing fixture zero offset, negative length --- .../with_zero_offset_length_negative.php.inc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc new file mode 100644 index 00000000..733607f1 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc @@ -0,0 +1,33 @@ + +----- + From d722c32a0eb06cae06820cc8a26d34eab0d2d717 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 20 Oct 2025 17:05:44 +0700 Subject: [PATCH 28/29] add failing fixture zero offset, negative length --- .../Fixture/with_zero_offset_length_negative.php.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc index 733607f1..1119a039 100644 --- a/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc +++ b/rules-tests/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector/Fixture/with_zero_offset_length_negative.php.inc @@ -9,7 +9,7 @@ class WithZeroOffsetLengthNegative { public function run(string $name) { - return substr($name, 0, -1); + return substr($name, 0, -5); } } @@ -26,7 +26,7 @@ class WithZeroOffsetLengthNegative { public function run(string $name) { - return (string) substr($name, 0, -1); + return (string) substr($name, 0, -5); } } From 04657b223db16404818d6d9cf0b6d5dd8e23f105 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 20 Oct 2025 17:06:43 +0700 Subject: [PATCH 29/29] fix --- .../FuncCall/DowngradeSubstrFalsyRector.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php index f83b2c19..9c20aeed 100644 --- a/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php +++ b/rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php @@ -199,15 +199,17 @@ public function refactor(Node $node): ?Node if ($offset instanceof Arg) { $offsetType = $this->getType($offset->value); if ($offsetType instanceof ConstantIntegerType && $offsetType->getValue() <= 0) { - return null; - } - - $length = $node->getArg('length', 2); - if ($length instanceof Arg) { - $lengthType = $this->getType($length->value); - if ($lengthType instanceof ConstantIntegerType && $lengthType->getValue() >= 0) { - return null; + $length = $node->getArg('length', 2); + if ($length instanceof Arg) { + $lengthType = $this->getType($length->value); + if ($lengthType instanceof ConstantIntegerType && $lengthType->getValue() >= 0) { + return null; + } + + return new String_($node); } + + return null; } }