diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 1a31978205..ed8dba3e7a 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1403,6 +1403,45 @@ private function specifyTypesForCountFuncCall( $resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); } + if ($context->truthy() && $isConstantArray->yes() && $isList->yes()) { + $hasOptionalKeys = false; + foreach ($type->getConstantArrays() as $arrayType) { + if ($arrayType->getOptionalKeys() !== []) { + $hasOptionalKeys = true; + break; + } + } + + if (!$hasOptionalKeys) { + $argExpr = $countFuncCall->getArgs()[0]->value; + $argExprString = $this->exprPrinter->printExpr($argExpr); + + $sizeMin = null; + $sizeMax = null; + if ($sizeType instanceof ConstantIntegerType) { + $sizeMin = $sizeType->getValue(); + $sizeMax = $sizeType->getValue(); + } elseif ($sizeType instanceof IntegerRangeType) { + $sizeMin = $sizeType->getMin(); + $sizeMax = $sizeType->getMax(); + } + + $sureTypes = []; + $sureNotTypes = []; + + if ($sizeMin !== null && $sizeMin >= 1) { + $sureTypes[$argExprString] = [$argExpr, new HasOffsetValueType(new ConstantIntegerType($sizeMin - 1), new MixedType())]; + } + if ($sizeMax !== null) { + $sureNotTypes[$argExprString] = [$argExpr, new HasOffsetValueType(new ConstantIntegerType($sizeMax), new MixedType())]; + } + + if ($sureTypes !== [] || $sureNotTypes !== []) { + return (new SpecifiedTypes($sureTypes, $sureNotTypes))->setRootExpr($rootExpr); + } + } + } + return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$resultTypes), $context, $scope)->setRootExpr($rootExpr); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-14301.php b/tests/PHPStan/Analyser/nsrt/bug-14301.php new file mode 100644 index 0000000000..9d90413ff5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14301.php @@ -0,0 +1,50 @@ +