From ce026a8eef3128e0e9a354ec689c866bfdbaceae Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Mar 2025 11:40:06 +0100 Subject: [PATCH 1/3] Fix count() regression --- src/Analyser/TypeSpecifier.php | 2 ++ tests/PHPStan/Analyser/nsrt/list-count.php | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c016b2869e..d6c03c3b85 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1063,10 +1063,12 @@ private function specifyTypesForCountFuncCall( $isConstantArray = $type->isConstantArray(); $isList = $type->isList(); + $positiveInt = IntegerRangeType::fromInterval(1, null); if ( !$isNormalCount->yes() || (!$isConstantArray->yes() && !$isList->yes()) || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing + || !$positiveInt->isSuperTypeOf($sizeType)->yes() ) { return null; } diff --git a/tests/PHPStan/Analyser/nsrt/list-count.php b/tests/PHPStan/Analyser/nsrt/list-count.php index 6654e46378..99f7a452ec 100644 --- a/tests/PHPStan/Analyser/nsrt/list-count.php +++ b/tests/PHPStan/Analyser/nsrt/list-count.php @@ -369,7 +369,7 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t if (count($row) >= $maxThree) { assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } else { - assertType('list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } } @@ -386,3 +386,23 @@ protected function testOptionalKeysInUnionArrayWithIntRange($row, $twoOrThree): } } } + +class FooBug +{ + public int $totalExpectedRows = 0; + + /** @var list<\stdClass> */ + public array $importedDaySummaryRows = []; + + public function sayHello(): void + { + assertType('int', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + if ($this->totalExpectedRows !== count($this->importedDaySummaryRows)) { + assertType('int', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } + assertType('int', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } +} From c7174a80ef6baaad01030e1414e885720ff0b3d4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Mar 2025 11:46:08 +0100 Subject: [PATCH 2/3] zero or more --- src/Analyser/TypeSpecifier.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index d6c03c3b85..105204cee7 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1063,12 +1063,12 @@ private function specifyTypesForCountFuncCall( $isConstantArray = $type->isConstantArray(); $isList = $type->isList(); - $positiveInt = IntegerRangeType::fromInterval(1, null); + $zeroOrMore = IntegerRangeType::fromInterval(0, null); if ( !$isNormalCount->yes() || (!$isConstantArray->yes() && !$isList->yes()) || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing - || !$positiveInt->isSuperTypeOf($sizeType)->yes() + || !$zeroOrMore->isSuperTypeOf($sizeType)->yes() ) { return null; } From f2acaddba25e3b180a05bdf74bcbc5b923584df1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Mar 2025 12:08:14 +0100 Subject: [PATCH 3/3] another test --- tests/PHPStan/Analyser/nsrt/list-count.php | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/list-count.php b/tests/PHPStan/Analyser/nsrt/list-count.php index 99f7a452ec..b5496d324d 100644 --- a/tests/PHPStan/Analyser/nsrt/list-count.php +++ b/tests/PHPStan/Analyser/nsrt/list-count.php @@ -406,3 +406,26 @@ public function sayHello(): void assertType('list', $this->importedDaySummaryRows); } } + +class FooBugPositiveInt +{ + /** + * @var positive-int + */ + public int $totalExpectedRows = 1; + + /** @var list<\stdClass> */ + public array $importedDaySummaryRows = []; + + public function sayHello(): void + { + assertType('int<1, max>', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + if ($this->totalExpectedRows !== count($this->importedDaySummaryRows)) { + assertType('int<1, max>', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } + assertType('int<1, max>', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } +}