Skip to content

Commit deb8481

Browse files
committed
fixed count() type after array_pop/array_shift
1 parent 3225f1a commit deb8481

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use PhpParser\Node\Expr\Ternary;
3636
use PhpParser\Node\Expr\Variable;
3737
use PhpParser\Node\Name;
38+
use PhpParser\Node\Scalar\LNumber;
3839
use PhpParser\Node\Stmt\Break_;
3940
use PhpParser\Node\Stmt\Class_;
4041
use PhpParser\Node\Stmt\Continue_;
@@ -129,6 +130,7 @@
129130
use PHPStan\Type\Generic\GenericClassStringType;
130131
use PHPStan\Type\Generic\TemplateTypeHelper;
131132
use PHPStan\Type\Generic\TemplateTypeMap;
133+
use PHPStan\Type\IntegerRangeType;
132134
use PHPStan\Type\IntegerType;
133135
use PHPStan\Type\IntersectionType;
134136
use PHPStan\Type\MixedType;
@@ -391,7 +393,7 @@ private function processStmtNode(
391393
$nodeCallback($declare->value, $scope);
392394
if (
393395
$declare->key->name !== 'strict_types'
394-
|| !($declare->value instanceof Node\Scalar\LNumber)
396+
|| !($declare->value instanceof LNumber)
395397
|| $declare->value->value !== 1
396398
) {
397399
continue;
@@ -1817,6 +1819,10 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
18171819
) {
18181820
$arrayArg = $expr->getArgs()[0]->value;
18191821
$arrayArgType = $scope->getType($arrayArg);
1822+
1823+
$countExpr = new FuncCall(new Name('count'), [$expr->getArgs()[0]]);
1824+
$decrementedCountType = $scope->getType(new BinaryOp\Minus($countExpr, new LNumber(1)));
1825+
18201826
$scope = $scope->invalidateExpression($arrayArg);
18211827

18221828
$functionName = $functionReflection->getName();
@@ -1833,6 +1839,14 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
18331839
return $type;
18341840
});
18351841

1842+
$validCount = IntegerRangeType::fromInterval(0, null);
1843+
if ($validCount->isSuperTypeOf($decrementedCountType)->yes()) {
1844+
$scope = $scope->assignExpression(
1845+
$countExpr,
1846+
$decrementedCountType,
1847+
);
1848+
}
1849+
18361850
$scope = $scope->assignExpression(
18371851
$arrayArg,
18381852
$arrayArgType,
@@ -3577,7 +3591,7 @@ static function (): void {
35773591
$throwPoints = array_merge($throwPoints, $itemResult->getThrowPoints());
35783592

35793593
if ($arrayItem->key === null) {
3580-
$dimExpr = new Node\Scalar\LNumber($i);
3594+
$dimExpr = new LNumber($i);
35813595
} else {
35823596
$dimExpr = $arrayItem->key;
35833597
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,7 @@ public function dataFileAsserts(): iterable
10081008
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php');
10091009
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-key-exists.php');
10101010
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7909.php');
1011+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7804.php');
10111012
}
10121013

10131014
/**
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug7804;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/** @param array<int, string> $headers */
8+
function pop1(array $headers): void
9+
{
10+
if (count($headers) >= 4) {
11+
assertType('int<4, max>', count($headers));
12+
array_pop($headers);
13+
assertType('int<3, max>', count($headers));
14+
}
15+
}
16+
17+
/** @param array<int, string> $headers */
18+
function pop2(array $headers): void
19+
{
20+
assertType('int<0, max>', count($headers));
21+
array_pop($headers);
22+
assertType('int<0, max>', count($headers));
23+
}
24+
25+
/** @param array<int, string> $headers */
26+
function shift1(array $headers): void
27+
{
28+
if (count($headers) >= 4) {
29+
assertType('int<4, max>', count($headers));
30+
array_shift($headers);
31+
assertType('int<3, max>', count($headers));
32+
}
33+
}
34+
35+
36+
/** @param array<int, string> $headers */
37+
function shift2(array $headers): void
38+
{
39+
assertType('int<0, max>', count($headers));
40+
array_shift($headers);
41+
assertType('int<0, max>', count($headers));
42+
}

0 commit comments

Comments
 (0)