diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 73a34c2175..c016b2869e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1061,10 +1061,11 @@ private function specifyTypesForCountFuncCall( $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($type->getIterableValueType()->isArray()->negate()); } + $isConstantArray = $type->isConstantArray(); $isList = $type->isList(); if ( !$isNormalCount->yes() - || (!$type->isConstantArray()->yes() && !$isList->yes()) + || (!$isConstantArray->yes() && !$isList->yes()) || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing ) { return null; @@ -1082,9 +1083,12 @@ private function specifyTypesForCountFuncCall( } if ( - $isList->yes() - && $sizeType instanceof ConstantIntegerType + $sizeType instanceof ConstantIntegerType && $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT + && ( + $isList->yes() + || $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getValue() - 1))->yes() + ) ) { // turn optional offsets non-optional $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); @@ -1097,9 +1101,12 @@ private function specifyTypesForCountFuncCall( } if ( - $isList->yes() - && $sizeType instanceof IntegerRangeType + $sizeType instanceof IntegerRangeType && $sizeType->getMin() !== null + && ( + $isList->yes() + || $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMin() - 1))->yes() + ) ) { // turn optional offsets non-optional $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); diff --git a/tests/PHPStan/Analyser/nsrt/count-type.php b/tests/PHPStan/Analyser/nsrt/count-type.php index 859718b615..1deb2e8695 100644 --- a/tests/PHPStan/Analyser/nsrt/count-type.php +++ b/tests/PHPStan/Analyser/nsrt/count-type.php @@ -87,6 +87,16 @@ public function doBaz(array $arr): void assertType('1|2', count($arr)); } + public function constantArrayWhichCanBecomeList(string $h): void + { + preg_match('#^([a-z0-9-]+)\..+$#', $h, $matches); + if (count($matches) !== 2) { + return; + } + + assertType('array{string, non-empty-string}', $matches); + } + } /** diff --git a/tests/PHPStan/Analyser/nsrt/list-count.php b/tests/PHPStan/Analyser/nsrt/list-count.php index c51ea31efc..6654e46378 100644 --- a/tests/PHPStan/Analyser/nsrt/list-count.php +++ b/tests/PHPStan/Analyser/nsrt/list-count.php @@ -379,9 +379,8 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t */ protected function testOptionalKeysInUnionArrayWithIntRange($row, $twoOrThree): void { - // doesn't narrow because no list if (count($row) >= $twoOrThree) { - assertType('array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + assertType('array{0: int, 1: string|null, 2?: int|null}', $row); } else { assertType('array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}|array{string}', $row); }