Skip to content

Commit 5cc5312

Browse files
phpstan-botclaude
andcommitted
Apply symmetric fix for integer range generalization in both-directions case
When integer ranges expand in both directions across loop iterations, compute actual observed min/max bounds instead of widening to plain int. This mirrors the constant integers fix already applied earlier in the method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cb6972e commit 5cc5312

2 files changed

Lines changed: 76 additions & 7 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4160,8 +4160,8 @@ private function generalizeType(Type $a, Type $b, int $depth): Type
41604160
$max = $rangeMax;
41614161
}
41624162

4163-
$gotGreater = false;
4164-
$gotSmaller = false;
4163+
$newMin = $min;
4164+
$newMax = $max;
41654165
foreach ($integerRanges['b'] as $range) {
41664166
if ($range->getMin() === null) {
41674167
$rangeMin = PHP_INT_MIN;
@@ -4174,25 +4174,34 @@ private function generalizeType(Type $a, Type $b, int $depth): Type
41744174
$rangeMax = $range->getMax();
41754175
}
41764176

4177-
if ($rangeMax > $max) {
4178-
$gotGreater = true;
4177+
if ($rangeMax > $newMax) {
4178+
$newMax = $rangeMax;
41794179
}
4180-
if ($rangeMin >= $min) {
4180+
if ($rangeMin >= $newMin) {
41814181
continue;
41824182
}
41834183

4184-
$gotSmaller = true;
4184+
$newMin = $rangeMin;
41854185
}
41864186

4187+
$gotGreater = $newMax > $max;
4188+
$gotSmaller = $newMin < $min;
4189+
41874190
if ($min === PHP_INT_MIN) {
41884191
$min = null;
41894192
}
41904193
if ($max === PHP_INT_MAX) {
41914194
$max = null;
41924195
}
4196+
if ($newMin === PHP_INT_MIN) {
4197+
$newMin = null;
4198+
}
4199+
if ($newMax === PHP_INT_MAX) {
4200+
$newMax = null;
4201+
}
41934202

41944203
if ($gotGreater && $gotSmaller) {
4195-
$resultTypes[] = new IntegerType();
4204+
$resultTypes[] = IntegerRangeType::fromInterval($newMin, $newMax);
41964205
} elseif ($gotGreater) {
41974206
$resultTypes[] = IntegerRangeType::fromInterval($min, null);
41984207
} elseif ($gotSmaller) {

tests/PHPStan/Analyser/nsrt/bug-12163.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,66 @@ public function iterateRowColumnIndicesDecrementing(int $rows, int $columns, int
7070
}
7171
}
7272

73+
class Test4
74+
{
75+
/**
76+
* @param int<0, 10> $columnIndex
77+
*/
78+
public function integerRangeBothDirections(int $rows, int $columns, int $columnIndex): void
79+
{
80+
if ($rows < 1 || $columns < 1) return;
81+
$size = $rows * $columns;
82+
83+
for ($i = 0; $i < $size; $i++) {
84+
assertType('int<0, max>', $columnIndex);
85+
if ($columnIndex < $columns) {
86+
$columnIndex++;
87+
} else {
88+
$columnIndex--;
89+
}
90+
}
91+
}
92+
}
93+
94+
class Test5
95+
{
96+
/**
97+
* @param int<0, 10> $columnIndex
98+
*/
99+
public function integerRangeOnlyGreater(int $rows, int $columns, int $columnIndex): void
100+
{
101+
if ($rows < 1 || $columns < 1) return;
102+
$size = $rows * $columns;
103+
104+
for ($i = 0; $i < $size; $i++) {
105+
assertType('int<0, max>', $columnIndex);
106+
if ($columnIndex < $columns) {
107+
$columnIndex++;
108+
} else {
109+
$columnIndex = 5;
110+
}
111+
}
112+
}
113+
}
114+
115+
class Test6
116+
{
117+
/**
118+
* @param int<5, 10> $value
119+
*/
120+
public function integerRangeGrowsBothDirections(int $value): void
121+
{
122+
for ($i = 0; $i < 10; $i++) {
123+
assertType('int<min, 10>', $value);
124+
if ($value > 0) {
125+
$value = $value - 2;
126+
} else {
127+
$value = $value + 3;
128+
}
129+
}
130+
}
131+
}
132+
73133
class Bug12163
74134
{
75135
/**

0 commit comments

Comments
 (0)