diff --git a/src/Type/Php/ArrayCountValuesDynamicReturnTypeExtension.php b/src/Type/Php/ArrayCountValuesDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..2cc1df4870 --- /dev/null +++ b/src/Type/Php/ArrayCountValuesDynamicReturnTypeExtension.php @@ -0,0 +1,73 @@ +getName() === 'array_count_values'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $args = $functionCall->getArgs(); + + if (!isset($args[0])) { + return null; + } + + $inputType = $scope->getType($args[0]->value); + + $arrayTypes = $inputType->getArrays(); + + $outputTypes = []; + + foreach ($arrayTypes as $arrayType) { + $itemType = $arrayType->getItemType(); + + if ($itemType instanceof UnionType) { + $itemType = $itemType->filterTypes( + static fn ($type) => !$type->toArrayKey() instanceof ErrorType, + ); + } + + if ($itemType->toArrayKey() instanceof ErrorType) { + continue; + } + + $outputTypes[] = TypeCombinator::intersect( + new ArrayType($itemType, IntegerRangeType::fromInterval(1, null)), + new NonEmptyArrayType(), + ); + } + + if (count($outputTypes) === 0) { + return new ConstantArrayType([], []); + } + + return TypeCombinator::union(...$outputTypes); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/array-count-values.php b/tests/PHPStan/Analyser/nsrt/array-count-values.php new file mode 100644 index 0000000000..f752bac1ad --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-count-values.php @@ -0,0 +1,43 @@ +>', $ints); + +$strings = array_count_values(['one', 'two', 'two', 'three']); + +assertType('non-empty-array<\'one\'|\'three\'|\'two\', int<1, max>>', $strings); + +$objects = array_count_values([new \stdClass()]); + +assertType('array{}', $objects); + +/** + * @return array + */ +function returnsStringOrObjectArray(): array +{ + +} + +// Objects are ignored by array_count_values, with a warning emitted. +assertType('non-empty-array>', array_count_values(returnsStringOrObjectArray())); + +class StringableObject +{ + + public function __toString(): string + { + return 'string'; + } + +} + +// Stringable objects are ignored by array_count_values, with a warning emitted. +$stringable = array_count_values([new StringableObject(), 'string', 1]); + +assertType('non-empty-array<1|\'string\', int<1, max>>', $stringable);