From 43056c055288cbd14df178e336df97300b370acc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:23:47 +0000 Subject: [PATCH 1/3] Fix non-negative-int generalized to int in for loop - Fixed overly aggressive type generalization in MutatingScope::generalizeType() for constant integers when values expand in both directions across loop iterations - Instead of widening to plain `int`, now computes actual observed bounds, allowing the next iteration to correctly determine stable vs growing bounds - New regression test in tests/PHPStan/Analyser/nsrt/bug-12163.php Closes https://github.com/phpstan/phpstan/issues/12163 --- src/Analyser/MutatingScope.php | 14 +++++++++++- tests/PHPStan/Analyser/nsrt/bug-12163.php | 27 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12163.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f1dfb1f0da..a24fe0d9ef 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4112,7 +4112,19 @@ private function generalizeType(Type $a, Type $b, int $depth): Type } if ($gotGreater && $gotSmaller) { - $resultTypes[] = new IntegerType(); + $newMin = $min; + $newMax = $max; + foreach ($constantIntegers['b'] as $int) { + if ($int->getValue() < $newMin) { + $newMin = $int->getValue(); + } + if ($int->getValue() <= $newMax) { + continue; + } + + $newMax = $int->getValue(); + } + $resultTypes[] = IntegerRangeType::fromInterval($newMin, $newMax); } elseif ($gotGreater) { $resultTypes[] = IntegerRangeType::fromInterval($min, null); } elseif ($gotSmaller) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12163.php b/tests/PHPStan/Analyser/nsrt/bug-12163.php new file mode 100644 index 0000000000..84788a6eb3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12163.php @@ -0,0 +1,27 @@ +', $rowIndex); + assertType('int<0, max>', $columnIndex); + if ($columnIndex < $columns) { + $columnIndex++; + } else { + $columnIndex = 0; + $rowIndex++; + } + } + } +} From e4e21756853ba6d6a50caa2821465f82366583a3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Mar 2026 07:24:33 +0100 Subject: [PATCH 2/3] test invers --- tests/PHPStan/Analyser/nsrt/bug-12163.php | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-12163.php b/tests/PHPStan/Analyser/nsrt/bug-12163.php index 84788a6eb3..36d956c12f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12163.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12163.php @@ -6,7 +6,7 @@ class Test { - public function iterateRowColumnIndices(int $rows, int $columns): void + public function iterateRowColumnIndicesIncrementing(int $rows, int $columns): void { if ($rows < 1 || $columns < 1) return; $size = $rows * $columns; @@ -25,3 +25,25 @@ public function iterateRowColumnIndices(int $rows, int $columns): void } } } + +class Test2 +{ + public function iterateRowColumnIndicesDecrementing(int $rows, int $columns): void + { + if ($rows < 1 || $columns < 1) return; + $size = $rows * $columns; + + $rowIndex = 0; + $columnIndex = 0; + for ($i = 0; $i < $size; $i++) { + assertType('0', $rowIndex); + assertType('int', $columnIndex); + if ($columnIndex < $columns) { + $columnIndex--; + } else { + $columnIndex = 0; + $rowIndex++; + } + } + } +} From bec459ef6f201fe15040c89a4eaa62312bed12f4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Mar 2026 07:34:27 +0100 Subject: [PATCH 3/3] Update bug-12163.php --- tests/PHPStan/Analyser/nsrt/bug-12163.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-12163.php b/tests/PHPStan/Analyser/nsrt/bug-12163.php index 36d956c12f..5e443f03ea 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12163.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12163.php @@ -47,3 +47,25 @@ public function iterateRowColumnIndicesDecrementing(int $rows, int $columns): vo } } } + +class Test3 +{ + /** + * @param int<0, 30> $columnIndex + */ + public function iterateRowColumnIndicesDecrementing(int $rows, int $columns, int $columnIndex): void + { + if ($rows < 1 || $columns < 1) return; + $size = $rows * $columns; + + for ($i = 0; $i < $size; $i++) { + assertType('int', $columnIndex); + if ($columnIndex < 3) { + $columnIndex--; + } else { + $columnIndex = 0; + } + assertType('int', $columnIndex); + } + } +}