From 0b54a31c863816b42757351648607c0b2fb6304d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 2 Jan 2026 22:51:44 +0700 Subject: [PATCH 1/4] [DeadCode][Php80] Handle crash on mix ClassPropertyAssignToConstructorPromotionRector+RemoveParentDelegatingConstructorRector --- .../Fixture/fixture.php.inc | 34 +++++++++++++++++++ ...ropertyPromoRemoveDelegatingParentTest.php | 28 +++++++++++++++ .../Source/SomeParentWithEmptyConstruct.php | 18 ++++++++++ .../config/configured_rule.php | 11 ++++++ 4 files changed, 91 insertions(+) create mode 100644 tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Fixture/fixture.php.inc create mode 100644 tests/Issues/IssuePropertyPromoRemoveDelegatingParent/IssuePropertyPromoRemoveDelegatingParentTest.php create mode 100644 tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php create mode 100644 tests/Issues/IssuePropertyPromoRemoveDelegatingParent/config/configured_rule.php diff --git a/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Fixture/fixture.php.inc b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Fixture/fixture.php.inc new file mode 100644 index 00000000000..41d5f1d887c --- /dev/null +++ b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Fixture/fixture.php.inc @@ -0,0 +1,34 @@ +prop = $prop; + parent::__construct(); + } +} + +?> +----- + diff --git a/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/IssuePropertyPromoRemoveDelegatingParentTest.php b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/IssuePropertyPromoRemoveDelegatingParentTest.php new file mode 100644 index 00000000000..0231e5064e3 --- /dev/null +++ b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/IssuePropertyPromoRemoveDelegatingParentTest.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/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php new file mode 100644 index 00000000000..05acdf8608d --- /dev/null +++ b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php @@ -0,0 +1,18 @@ +init(); + } + + private function init(): void + { + echo 'A init'; + } +} \ No newline at end of file diff --git a/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/config/configured_rule.php b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/config/configured_rule.php new file mode 100644 index 00000000000..75e42bde090 --- /dev/null +++ b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/config/configured_rule.php @@ -0,0 +1,11 @@ +withRules([ + ClassPropertyAssignToConstructorPromotionRector::class, + RemoveParentDelegatingConstructorRector::class, + ]); \ No newline at end of file From b70e9b5901fdba42c90ce05c22bab9b933407757 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 2 Jan 2026 23:07:52 +0700 Subject: [PATCH 2/4] fix finalize class --- .../Source/SomeParentWithEmptyConstruct.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php index 05acdf8608d..05e4ddb58ae 100644 --- a/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php +++ b/tests/Issues/IssuePropertyPromoRemoveDelegatingParent/Source/SomeParentWithEmptyConstruct.php @@ -4,7 +4,7 @@ namespace Rector\Tests\Issues\IssuePropertyPromoRemoveDelegatingParent\Source; -class SomeParentWithEmptyConstruct +abstract class SomeParentWithEmptyConstruct { public function __construct() { From 1c7d2f0644dba19c32e64afe82a73c9f6fcdf9ad Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 3 Jan 2026 08:49:45 +0700 Subject: [PATCH 3/4] alternative fix: reindex in after refactor only, but via traverser --- src/Application/ChangedNodeScopeRefresher.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Application/ChangedNodeScopeRefresher.php b/src/Application/ChangedNodeScopeRefresher.php index ea1ea0293cb..624088b7f1f 100644 --- a/src/Application/ChangedNodeScopeRefresher.php +++ b/src/Application/ChangedNodeScopeRefresher.php @@ -58,7 +58,18 @@ public function refresh(Node $node, string $filePath, ?MutatingScope $mutatingSc throw new ShouldNotHappenException($errorMessage); } - NodeAttributeReIndexer::reIndexNodeAttributes($node); + /** + * The reindex is needed to: + * - be used by PHPStan processNodes() that relies on indexed arrays start from 0 + * - use traverser to avoid issues when multiples rules apply, and higher node reindex deep node, + * which the next rule use deep node, for example: + * - first rule: - Class_ → ClassMethod → remove stmt with index 0 + * - second rule: - ClassMethod → here fetch the index 0 that no longer exists + */ + SimpleCallableNodeTraverser::traverse( + $node, + fn (Node $subNode): ?Node => NodeAttributeReIndexer::reIndexNodeAttributes($subNode) + ); $stmts = $this->resolveStmts($node); $this->phpStanNodeScopeResolver->processNodes($stmts, $filePath, $mutatingScope); From 38169d3cf3d1ef7fd8365885740ddb820e636f8b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 3 Jan 2026 09:02:16 +0700 Subject: [PATCH 4/4] final touch: typo comment --- src/Application/ChangedNodeScopeRefresher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Application/ChangedNodeScopeRefresher.php b/src/Application/ChangedNodeScopeRefresher.php index 624088b7f1f..2fff52d0abb 100644 --- a/src/Application/ChangedNodeScopeRefresher.php +++ b/src/Application/ChangedNodeScopeRefresher.php @@ -61,7 +61,7 @@ public function refresh(Node $node, string $filePath, ?MutatingScope $mutatingSc /** * The reindex is needed to: * - be used by PHPStan processNodes() that relies on indexed arrays start from 0 - * - use traverser to avoid issues when multiples rules apply, and higher node reindex deep node, + * - use traverser to avoid issues when multiples rules apply, and higher node remove deep node, * which the next rule use deep node, for example: * - first rule: - Class_ → ClassMethod → remove stmt with index 0 * - second rule: - ClassMethod → here fetch the index 0 that no longer exists