Skip to content

Commit 67e510d

Browse files
committed
Fix #14234: Infer list offset exists for $i < count($list) - N
- Added TypeSpecifier handling for BinaryOp\Minus with count() on the left side - When $i < count($list) - N (N >= 0), $list[$i] is narrowed as existing - New regression test in tests/PHPStan/Rules/Arrays/data/bug-14234.php
1 parent 42d07e4 commit 67e510d

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,33 @@ public function specifyTypesInCondition(
351351
}
352352
}
353353

354+
// infer $list[$index] after $index < count($list) - N
355+
if (
356+
$context->true()
357+
&& !$orEqual
358+
&& $expr->right instanceof Expr\BinaryOp\Minus
359+
&& $expr->right->left instanceof FuncCall
360+
&& $expr->right->left->name instanceof Name
361+
&& in_array(strtolower((string) $expr->right->left->name), ['count', 'sizeof'], true)
362+
&& count($expr->right->left->getArgs()) >= 1
363+
&& $leftType->isInteger()->yes()
364+
&& !$leftType instanceof ConstantIntegerType
365+
&& IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()
366+
) {
367+
$countArgType = $scope->getType($expr->right->left->getArgs()[0]->value);
368+
$subtractedType = $scope->getType($expr->right->right);
369+
if (
370+
$countArgType->isList()->yes()
371+
&& IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($subtractedType)->yes()
372+
) {
373+
$arrayArg = $expr->right->left->getArgs()[0]->value;
374+
$dimFetch = new ArrayDimFetch($arrayArg, $expr->left);
375+
$result = $result->unionWith(
376+
$this->create($dimFetch, $countArgType->getIterableValueType(), TypeSpecifierContext::createTrue(), $scope)->setRootExpr($expr),
377+
);
378+
}
379+
}
380+
354381
if (
355382
!$context->null()
356383
&& $expr->right instanceof FuncCall

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,4 +1189,11 @@ public function testBug13770(): void
11891189
]);
11901190
}
11911191

1192+
public function testBug14234(): void
1193+
{
1194+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
1195+
1196+
$this->analyse([__DIR__ . '/data/bug-14234.php'], []);
1197+
}
1198+
11921199
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14234;
4+
5+
function getShortenedPath(string $identifier): string
6+
{
7+
$parts = explode('/', $identifier);
8+
9+
for ($i = 0; $i < count($parts) - 1; $i++) {
10+
$parts[$i] = substr($parts[$i], 0, 1);
11+
}
12+
13+
return implode("/", $parts);
14+
}
15+
16+
function getShortenedPath2(string $identifier): string
17+
{
18+
$parts = explode('/', $identifier);
19+
20+
for ($i = 0; $i < count($parts); $i++) {
21+
$parts[$i] = substr($parts[$i], 0, 1);
22+
}
23+
24+
return implode("/", $parts);
25+
}

0 commit comments

Comments
 (0)