From fff32ed1d55ef40304b6e532adb71042e3c996c9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Sep 2025 12:07:12 +0200 Subject: [PATCH 1/6] Skip deep expression when root expression changes while scope generalization --- src/Analyser/MutatingScope.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 008704829b..4b0a607b79 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5296,14 +5296,35 @@ private function generalizeVariableTypeHolders( array $otherVariableTypeHolders, ): array { + $sortByExprStringLength = static function (string $exprA, string $exprB): int { + return strlen($exprA) <=> strlen($exprB); + }; + + uksort($variableTypeHolders, $sortByExprStringLength); + uksort($otherVariableTypeHolders, $sortByExprStringLength); + + $changedArrays = []; foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { if (!isset($otherVariableTypeHolders[$variableExprString])) { continue; } + foreach($changedArrays as $changedExpr) { + if (str_starts_with($variableExprString, $changedExpr)) { + continue 2; + } + } + + $generalizedType = $this->generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0); + if ( + $generalizedType->isArray()->yes() && + !$generalizedType->equals($variableTypeHolder->getType())) + { + $changedArrays[] = $variableExprString; + } $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $variableTypeHolder->getExpr(), - $this->generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0), + $generalizedType, $variableTypeHolder->getCertainty(), ); } From 44f7c6a1100b0a7d7d83e51d74096ef14386cf41 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Sep 2025 12:17:02 +0200 Subject: [PATCH 2/6] expectations --- tests/PHPStan/Analyser/nsrt/pr-4372.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/pr-4372.php diff --git a/tests/PHPStan/Analyser/nsrt/pr-4372.php b/tests/PHPStan/Analyser/nsrt/pr-4372.php new file mode 100644 index 0000000000..155b559525 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pr-4372.php @@ -0,0 +1,19 @@ +, non-empty-array, string>>', $locations); + assertType('non-empty-array, string>>', $locations[0]); +}; From d6d89aeacc02bf255113227f41613fb9fd2f6a9d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Sep 2025 18:20:30 +0200 Subject: [PATCH 3/6] fix --- src/Analyser/MutatingScope.php | 22 +++++++++---------- .../nsrt/{pr-4372.php => pr-4390.php} | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) rename tests/PHPStan/Analyser/nsrt/{pr-4372.php => pr-4390.php} (80%) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 4b0a607b79..7093a922d1 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -162,12 +162,14 @@ use function ltrim; use function md5; use function sprintf; +use function str_contains; use function str_decrement; use function str_increment; use function str_starts_with; use function strlen; use function strtolower; use function substr; +use function uksort; use function usort; use const PHP_INT_MAX; use const PHP_INT_MIN; @@ -5296,12 +5298,7 @@ private function generalizeVariableTypeHolders( array $otherVariableTypeHolders, ): array { - $sortByExprStringLength = static function (string $exprA, string $exprB): int { - return strlen($exprA) <=> strlen($exprB); - }; - - uksort($variableTypeHolders, $sortByExprStringLength); - uksort($otherVariableTypeHolders, $sortByExprStringLength); + uksort($variableTypeHolders, static fn (string $exprA, string $exprB): int => strlen($exprA) <=> strlen($exprB)); $changedArrays = []; foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { @@ -5309,17 +5306,20 @@ private function generalizeVariableTypeHolders( continue; } - foreach($changedArrays as $changedExpr) { - if (str_starts_with($variableExprString, $changedExpr)) { + foreach ($changedArrays as $changedExpr) { + if ( + str_contains($variableExprString, $changedExpr . '[') + || str_contains($variableExprString, '[' . $changedExpr . ']') + ) { + unset($variableTypeHolders[$variableExprString]); continue 2; } } $generalizedType = $this->generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0); if ( - $generalizedType->isArray()->yes() && - !$generalizedType->equals($variableTypeHolder->getType())) - { + !$generalizedType->equals($variableTypeHolder->getType()) + ) { $changedArrays[] = $variableExprString; } $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( diff --git a/tests/PHPStan/Analyser/nsrt/pr-4372.php b/tests/PHPStan/Analyser/nsrt/pr-4390.php similarity index 80% rename from tests/PHPStan/Analyser/nsrt/pr-4372.php rename to tests/PHPStan/Analyser/nsrt/pr-4390.php index 155b559525..bf0824e0df 100644 --- a/tests/PHPStan/Analyser/nsrt/pr-4372.php +++ b/tests/PHPStan/Analyser/nsrt/pr-4390.php @@ -1,6 +1,6 @@ , non-empty-array, string>>', $locations); - assertType('non-empty-array, string>>', $locations[0]); + assertType('non-empty-array, string>', $locations[0]); }; From 325063b53ae0900318bf7feb9282f73f55d13c87 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 29 Sep 2025 18:23:12 +0200 Subject: [PATCH 4/6] Update MutatingScope.php --- src/Analyser/MutatingScope.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 7093a922d1..c3a78b82a6 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5300,13 +5300,13 @@ private function generalizeVariableTypeHolders( { uksort($variableTypeHolders, static fn (string $exprA, string $exprB): int => strlen($exprA) <=> strlen($exprB)); - $changedArrays = []; + $generalizedExpressions = []; foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { if (!isset($otherVariableTypeHolders[$variableExprString])) { continue; } - foreach ($changedArrays as $changedExpr) { + foreach ($generalizedExpressions as $changedExpr) { if ( str_contains($variableExprString, $changedExpr . '[') || str_contains($variableExprString, '[' . $changedExpr . ']') @@ -5320,7 +5320,7 @@ private function generalizeVariableTypeHolders( if ( !$generalizedType->equals($variableTypeHolder->getType()) ) { - $changedArrays[] = $variableExprString; + $generalizedExpressions[] = $variableExprString; } $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $variableTypeHolder->getExpr(), From 922cd6a81944c4d94cb5066b6750513bd3edf823 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 30 Sep 2025 09:54:45 +0200 Subject: [PATCH 5/6] Better logic --- src/Analyser/MutatingScope.php | 26 ++++++++++++------------- tests/PHPStan/Analyser/nsrt/pr-4390.php | 1 - 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c3a78b82a6..a15fcd4b8a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -162,7 +162,6 @@ use function ltrim; use function md5; use function sprintf; -use function str_contains; use function str_decrement; use function str_increment; use function str_starts_with; @@ -5301,35 +5300,34 @@ private function generalizeVariableTypeHolders( uksort($variableTypeHolders, static fn (string $exprA, string $exprB): int => strlen($exprA) <=> strlen($exprB)); $generalizedExpressions = []; + $newVariableTypeHolders = []; foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { + foreach ($generalizedExpressions as $generalizedExprString => $generalizedTypeHolder) { + if (!$this->shouldInvalidateExpression($generalizedExprString, $generalizedTypeHolder->getExpr(), $variableTypeHolder->getExpr())) { + continue; + } + + continue 2; + } if (!isset($otherVariableTypeHolders[$variableExprString])) { + $newVariableTypeHolders[$variableExprString] = $variableTypeHolder; continue; } - foreach ($generalizedExpressions as $changedExpr) { - if ( - str_contains($variableExprString, $changedExpr . '[') - || str_contains($variableExprString, '[' . $changedExpr . ']') - ) { - unset($variableTypeHolders[$variableExprString]); - continue 2; - } - } - $generalizedType = $this->generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0); if ( !$generalizedType->equals($variableTypeHolder->getType()) ) { - $generalizedExpressions[] = $variableExprString; + $generalizedExpressions[$variableExprString] = $variableTypeHolder; } - $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( + $newVariableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $variableTypeHolder->getExpr(), $generalizedType, $variableTypeHolder->getCertainty(), ); } - return $variableTypeHolders; + return $newVariableTypeHolders; } private function generalizeType(Type $a, Type $b, int $depth): Type diff --git a/tests/PHPStan/Analyser/nsrt/pr-4390.php b/tests/PHPStan/Analyser/nsrt/pr-4390.php index bf0824e0df..c318b9b6ee 100644 --- a/tests/PHPStan/Analyser/nsrt/pr-4390.php +++ b/tests/PHPStan/Analyser/nsrt/pr-4390.php @@ -2,7 +2,6 @@ namespace PR4390; -use function PHPStan\debugScope; use function PHPStan\Testing\assertType; function (string $s): void { From a63e8c6d900142fb22f2051cabd5e969ce831b85 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 30 Sep 2025 10:13:04 +0200 Subject: [PATCH 6/6] Improvement --- src/Analyser/MutatingScope.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a15fcd4b8a..c40ad28c13 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5302,8 +5302,8 @@ private function generalizeVariableTypeHolders( $generalizedExpressions = []; $newVariableTypeHolders = []; foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) { - foreach ($generalizedExpressions as $generalizedExprString => $generalizedTypeHolder) { - if (!$this->shouldInvalidateExpression($generalizedExprString, $generalizedTypeHolder->getExpr(), $variableTypeHolder->getExpr())) { + foreach ($generalizedExpressions as $generalizedExprString => $generalizedExpr) { + if (!$this->shouldInvalidateExpression($generalizedExprString, $generalizedExpr, $variableTypeHolder->getExpr())) { continue; } @@ -5318,7 +5318,7 @@ private function generalizeVariableTypeHolders( if ( !$generalizedType->equals($variableTypeHolder->getType()) ) { - $generalizedExpressions[$variableExprString] = $variableTypeHolder; + $generalizedExpressions[$variableExprString] = $variableTypeHolder->getExpr(); } $newVariableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $variableTypeHolder->getExpr(),