Skip to content

Commit 9d660fb

Browse files
authored
Fix phpstan/phpstan#12163: non-negative-int evaluated to int (#5176)
1 parent 8d69bd0 commit 9d660fb

File tree

3 files changed

+198
-17
lines changed

3 files changed

+198
-17
lines changed

src/Analyser/MutatingScope.php

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4104,24 +4104,24 @@ private function generalizeType(Type $a, Type $b, int $depth): Type
41044104
$max = $int->getValue();
41054105
}
41064106

4107-
$gotGreater = false;
4108-
$gotSmaller = false;
4107+
$newMin = $min;
4108+
$newMax = $max;
41094109
foreach ($constantIntegers['b'] as $int) {
4110-
if ($int->getValue() > $max) {
4111-
$gotGreater = true;
4110+
if ($int->getValue() > $newMax) {
4111+
$newMax = $int->getValue();
41124112
}
4113-
if ($int->getValue() >= $min) {
4113+
if ($int->getValue() >= $newMin) {
41144114
continue;
41154115
}
41164116

4117-
$gotSmaller = true;
4117+
$newMin = $int->getValue();
41184118
}
41194119

4120-
if ($gotGreater && $gotSmaller) {
4121-
$resultTypes[] = new IntegerType();
4122-
} elseif ($gotGreater) {
4120+
if ($newMax > $max && $newMin < $min) {
4121+
$resultTypes[] = IntegerRangeType::fromInterval($newMin, $newMax);
4122+
} elseif ($newMax > $max) {
41234123
$resultTypes[] = IntegerRangeType::fromInterval($min, null);
4124-
} elseif ($gotSmaller) {
4124+
} elseif ($newMin < $min) {
41254125
$resultTypes[] = IntegerRangeType::fromInterval(null, $max);
41264126
} else {
41274127
$resultTypes[] = TypeCombinator::union($constantIntegersA, $constantIntegersB);
@@ -4166,8 +4166,8 @@ private function generalizeType(Type $a, Type $b, int $depth): Type
41664166
$max = $rangeMax;
41674167
}
41684168

4169-
$gotGreater = false;
4170-
$gotSmaller = false;
4169+
$newMin = $min;
4170+
$newMax = $max;
41714171
foreach ($integerRanges['b'] as $range) {
41724172
if ($range->getMin() === null) {
41734173
$rangeMin = PHP_INT_MIN;
@@ -4180,25 +4180,34 @@ private function generalizeType(Type $a, Type $b, int $depth): Type
41804180
$rangeMax = $range->getMax();
41814181
}
41824182

4183-
if ($rangeMax > $max) {
4184-
$gotGreater = true;
4183+
if ($rangeMax > $newMax) {
4184+
$newMax = $rangeMax;
41854185
}
4186-
if ($rangeMin >= $min) {
4186+
if ($rangeMin >= $newMin) {
41874187
continue;
41884188
}
41894189

4190-
$gotSmaller = true;
4190+
$newMin = $rangeMin;
41914191
}
41924192

4193+
$gotGreater = $newMax > $max;
4194+
$gotSmaller = $newMin < $min;
4195+
41934196
if ($min === PHP_INT_MIN) {
41944197
$min = null;
41954198
}
41964199
if ($max === PHP_INT_MAX) {
41974200
$max = null;
41984201
}
4202+
if ($newMin === PHP_INT_MIN) {
4203+
$newMin = null;
4204+
}
4205+
if ($newMax === PHP_INT_MAX) {
4206+
$newMax = null;
4207+
}
41994208

42004209
if ($gotGreater && $gotSmaller) {
4201-
$resultTypes[] = new IntegerType();
4210+
$resultTypes[] = IntegerRangeType::fromInterval($newMin, $newMax);
42024211
} elseif ($gotGreater) {
42034212
$resultTypes[] = IntegerRangeType::fromInterval($min, null);
42044213
} elseif ($gotSmaller) {
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12163;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Test
8+
{
9+
public function iterateRowColumnIndicesIncrementing(int $rows, int $columns): void
10+
{
11+
if ($rows < 1 || $columns < 1) return;
12+
$size = $rows * $columns;
13+
14+
$rowIndex = 0;
15+
$columnIndex = 0;
16+
for ($i = 0; $i < $size; $i++) {
17+
assertType('int<0, max>', $rowIndex);
18+
assertType('int<0, max>', $columnIndex);
19+
if ($columnIndex < $columns) {
20+
$columnIndex++;
21+
} else {
22+
$columnIndex = 0;
23+
$rowIndex++;
24+
}
25+
}
26+
}
27+
}
28+
29+
class Test2
30+
{
31+
public function iterateRowColumnIndicesDecrementing(int $rows, int $columns): void
32+
{
33+
if ($rows < 1 || $columns < 1) return;
34+
$size = $rows * $columns;
35+
36+
$rowIndex = 0;
37+
$columnIndex = 0;
38+
for ($i = 0; $i < $size; $i++) {
39+
assertType('0', $rowIndex); // `0`, because the IF in line 41 is always TRUE
40+
assertType('int<min, 0>', $columnIndex);
41+
if ($columnIndex < $columns) {
42+
$columnIndex--;
43+
} else {
44+
$columnIndex = 0;
45+
$rowIndex++;
46+
}
47+
}
48+
}
49+
}
50+
51+
class Test3
52+
{
53+
/**
54+
* @param int<0, 30> $columnIndex
55+
*/
56+
public function iterateRowColumnIndicesDecrementing(int $rows, int $columns, int $columnIndex): void
57+
{
58+
if ($rows < 1 || $columns < 1) return;
59+
$size = $rows * $columns;
60+
61+
for ($i = 0; $i < $size; $i++) {
62+
assertType('int<min, 30>', $columnIndex);
63+
if ($columnIndex < 3) {
64+
$columnIndex--;
65+
} else {
66+
$columnIndex = 0;
67+
}
68+
assertType('int<min, 1>', $columnIndex);
69+
}
70+
}
71+
}
72+
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+
133+
class Bug12163
134+
{
135+
/**
136+
* @param non-negative-int $value
137+
* @return void
138+
*/
139+
private function checkNonNegative(int $value): void
140+
{
141+
sleep(1);
142+
}
143+
144+
public function iterateRowColumnIndices(int $rows, int $columns): void
145+
{
146+
if ($rows < 1 || $columns < 1) return;
147+
$size = $rows * $columns;
148+
149+
$rowIndex = 0;
150+
$columnIndex = 0;
151+
for ($i = 0; $i < $size; $i++) {
152+
$this->checkNonNegative($rowIndex);
153+
$this->checkNonNegative($columnIndex);
154+
if ($columnIndex < $columns) {
155+
$columnIndex++;
156+
} else {
157+
$columnIndex = 0;
158+
$rowIndex++;
159+
}
160+
}
161+
}
162+
}

tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,14 @@ public function testBug13874(): void
288288
$this->analyse([__DIR__ . '/data/bug-13874.php'], []);
289289
}
290290

291+
public function testBug12163(): void
292+
{
293+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12163.php'], [
294+
[
295+
'Comparison operation "<" between int<min, 0> and int<1, max> is always true.',
296+
41,
297+
],
298+
]);
299+
}
300+
291301
}

0 commit comments

Comments
 (0)