Skip to content

Commit c93be94

Browse files
committed
Fix false positive for list offset access after $index < count($list)
- Added type specification in TypeSpecifier for list dim fetch when a non-negative integer variable is compared with count() using strict less-than - When $index < count($list) holds and $index is non-negative, $list[$index] is guaranteed to be a valid offset for a list (sequential 0-based keys) - Excluded ConstantIntegerType left operands since those are already handled by specifyTypesForCountFuncCall - New regression test in tests/PHPStan/Rules/Arrays/data/bug-13770.php
1 parent 8eb56e5 commit c93be94

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,21 @@ public function specifyTypesInCondition(
333333
);
334334
}
335335
}
336+
337+
// infer $list[$index] after $index < count($list)
338+
if (
339+
$context->true()
340+
&& !$orEqual
341+
&& !$leftType instanceof ConstantIntegerType
342+
&& $argType->isList()->yes()
343+
&& IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()
344+
) {
345+
$arrayArg = $expr->right->getArgs()[0]->value;
346+
$dimFetch = new ArrayDimFetch($arrayArg, $expr->left);
347+
$result = $result->unionWith(
348+
$this->create($dimFetch, $argType->getIterableValueType(), TypeSpecifierContext::createTrue(), $scope)->setRootExpr($expr),
349+
);
350+
}
336351
}
337352

338353
if (

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,4 +1161,20 @@ public function testBug13526(): void
11611161
$this->analyse([__DIR__ . '/data/bug-13526.php'], []);
11621162
}
11631163

1164+
public function testBug13770(): void
1165+
{
1166+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
1167+
1168+
$this->analyse([__DIR__ . '/data/bug-13770.php'], [
1169+
[
1170+
'Offset int<1, max> might not exist on non-empty-list<int>.',
1171+
40,
1172+
],
1173+
[
1174+
'Offset int might not exist on list<int>.',
1175+
53,
1176+
],
1177+
]);
1178+
}
1179+
11641180
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13770;
4+
5+
class HelloWorld
6+
{
7+
/**
8+
* @param list<int> $array
9+
* @param positive-int $index
10+
*/
11+
public function positiveIntLessThanCount(array $array, int $index): int
12+
{
13+
if ($index < count($array)) {
14+
return $array[$index]; // should not report
15+
}
16+
17+
return 0;
18+
}
19+
20+
/**
21+
* @param list<int> $array
22+
* @param int<0, max> $index
23+
*/
24+
public function nonNegativeIntLessThanCount(array $array, int $index): int
25+
{
26+
if ($index < count($array)) {
27+
return $array[$index]; // should not report
28+
}
29+
30+
return 0;
31+
}
32+
33+
/**
34+
* @param list<int> $array
35+
* @param positive-int $index
36+
*/
37+
public function positiveIntLessThanOrEqualCount(array $array, int $index): int
38+
{
39+
if ($index <= count($array)) {
40+
return $array[$index]; // SHOULD still report - off by one
41+
}
42+
43+
return 0;
44+
}
45+
46+
/**
47+
* @param list<int> $array
48+
* @param int $index
49+
*/
50+
public function anyIntLessThanCount(array $array, int $index): int
51+
{
52+
if ($index < count($array)) {
53+
return $array[$index]; // SHOULD still report - could be negative
54+
}
55+
56+
return 0;
57+
}
58+
}

0 commit comments

Comments
 (0)