From deb84815fe3cb245a665481fbefedaace872cce0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 4 Sep 2022 11:14:51 +0200 Subject: [PATCH] fixed count() type after array_pop/array_shift --- src/Analyser/NodeScopeResolver.php | 18 +++++++- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-7804.php | 42 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-7804.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a6a8cbb2d1..5edceab5d7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -35,6 +35,7 @@ use PhpParser\Node\Expr\Ternary; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; +use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Stmt\Break_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Continue_; @@ -129,6 +130,7 @@ use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; @@ -391,7 +393,7 @@ private function processStmtNode( $nodeCallback($declare->value, $scope); if ( $declare->key->name !== 'strict_types' - || !($declare->value instanceof Node\Scalar\LNumber) + || !($declare->value instanceof LNumber) || $declare->value->value !== 1 ) { continue; @@ -1817,6 +1819,10 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression ) { $arrayArg = $expr->getArgs()[0]->value; $arrayArgType = $scope->getType($arrayArg); + + $countExpr = new FuncCall(new Name('count'), [$expr->getArgs()[0]]); + $decrementedCountType = $scope->getType(new BinaryOp\Minus($countExpr, new LNumber(1))); + $scope = $scope->invalidateExpression($arrayArg); $functionName = $functionReflection->getName(); @@ -1833,6 +1839,14 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression return $type; }); + $validCount = IntegerRangeType::fromInterval(0, null); + if ($validCount->isSuperTypeOf($decrementedCountType)->yes()) { + $scope = $scope->assignExpression( + $countExpr, + $decrementedCountType, + ); + } + $scope = $scope->assignExpression( $arrayArg, $arrayArgType, @@ -3577,7 +3591,7 @@ static function (): void { $throwPoints = array_merge($throwPoints, $itemResult->getThrowPoints()); if ($arrayItem->key === null) { - $dimExpr = new Node\Scalar\LNumber($i); + $dimExpr = new LNumber($i); } else { $dimExpr = $arrayItem->key; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 3ea85cba74..8663510f5d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1008,6 +1008,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-key-exists.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7909.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7804.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-7804.php b/tests/PHPStan/Analyser/data/bug-7804.php new file mode 100644 index 0000000000..bd59e4bd9f --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7804.php @@ -0,0 +1,42 @@ + $headers */ +function pop1(array $headers): void +{ + if (count($headers) >= 4) { + assertType('int<4, max>', count($headers)); + array_pop($headers); + assertType('int<3, max>', count($headers)); + } +} + +/** @param array $headers */ +function pop2(array $headers): void +{ + assertType('int<0, max>', count($headers)); + array_pop($headers); + assertType('int<0, max>', count($headers)); +} + +/** @param array $headers */ +function shift1(array $headers): void +{ + if (count($headers) >= 4) { + assertType('int<4, max>', count($headers)); + array_shift($headers); + assertType('int<3, max>', count($headers)); + } +} + + +/** @param array $headers */ +function shift2(array $headers): void +{ + assertType('int<0, max>', count($headers)); + array_shift($headers); + assertType('int<0, max>', count($headers)); +}