diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 5ce9b20762c..cfbce40dcc7 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -418,9 +418,7 @@ 'array_uintersect_assoc\'1' => ['array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable', '...rest='=>'array|callable(mixed,mixed):int'], 'array_uintersect_uassoc' => ['array', 'array'=>'array', 'rest'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], 'array_uintersect_uassoc\'1' => ['array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], -'array_unique' => ['array', 'array'=>'array', 'flags='=>'0'], -'array_unique\'1' => ['array', 'array'=>'array', 'flags='=>'1'], -'array_unique\'2' => ['array', 'array'=>'array', 'flags='=>'2|5'], +'array_unique' => ['array', 'array'=>'array', 'flags='=>'int'], 'array_unshift' => ['int', '&rw_array'=>'array', '...values='=>'mixed'], 'array_values' => ['list', 'array'=>'array'], 'array_walk' => ['bool', '&rw_array'=>'array', 'callback'=>'callable', 'arg='=>'mixed'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 075c0a161bd..b1c8b9488ff 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -9381,9 +9381,7 @@ 'array_uintersect_assoc\'1' => ['array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable', '...rest='=>'array|callable(mixed,mixed):int'], 'array_uintersect_uassoc' => ['array', 'array'=>'array', 'rest'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], 'array_uintersect_uassoc\'1' => ['array', 'array'=>'array', 'rest'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], - 'array_unique' => ['array', 'array'=>'array', 'flags='=>'0'], - 'array_unique\'1' => ['array', 'array'=>'array', 'flags='=>'1'], - 'array_unique\'2' => ['array', 'array'=>'array', 'flags='=>'2|5'], + 'array_unique' => ['array', 'array'=>'array', 'flags='=>'int'], 'array_unshift' => ['int', '&rw_array'=>'array', '...values'=>'mixed'], 'array_values' => ['list', 'array'=>'array'], 'array_walk' => ['bool', '&rw_array'=>'array', 'callback'=>'callable', 'arg='=>'mixed'], diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index bdd59a0abc4..0d2552d8e48 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -22,7 +22,6 @@ use Psalm\Internal\Provider\ReturnTypeProvider\ArrayReverseReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ArraySliceReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ArraySpliceReturnTypeProvider; -use Psalm\Internal\Provider\ReturnTypeProvider\ArrayUniqueReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\BasenameReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\DirnameReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\FilterVarReturnTypeProvider; @@ -81,7 +80,6 @@ public function __construct() $this->registerClass(ArraySliceReturnTypeProvider::class); $this->registerClass(ArraySpliceReturnTypeProvider::class); $this->registerClass(ArrayReverseReturnTypeProvider::class); - $this->registerClass(ArrayUniqueReturnTypeProvider::class); $this->registerClass(ArrayFillReturnTypeProvider::class); $this->registerClass(ArrayFillKeysReturnTypeProvider::class); $this->registerClass(FilterVarReturnTypeProvider::class); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php deleted file mode 100644 index 1b46a3fb901..00000000000 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ - public static function getFunctionIds(): array - { - return ['array_unique']; - } - - public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): Union - { - $statements_source = $event->getStatementsSource(); - $call_args = $event->getCallArgs(); - if (!$statements_source instanceof StatementsAnalyzer) { - return Type::getMixed(); - } - - $first_arg = $call_args[0]->value ?? null; - - $first_arg_array = $first_arg - && ($first_arg_type = $statements_source->node_data->getType($first_arg)) - && $first_arg_type->hasType('array') - && ($array_atomic_type = $first_arg_type->getArray()) - && ($array_atomic_type instanceof TArray - || $array_atomic_type instanceof TKeyedArray) - ? $array_atomic_type - : null; - - if (!$first_arg_array) { - return Type::getArray(); - } - - if ($first_arg_array instanceof TArray) { - if ($first_arg_array instanceof TNonEmptyArray) { - $first_arg_array = $first_arg_array->setCount(null); - } - - return new Union([$first_arg_array]); - } - - return new Union([$first_arg_array->getGenericArrayType()]); - } -} diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index bffe59a41a8..c72fa03ffa6 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -129,6 +129,20 @@ function array_flip(array $array) { } +/** + * @psalm-template TKey as array-key + * @psalm-template TValue + * @psalm-template TArray as array + * + * @param TArray $array + * + * @return (TArray is non-empty-array ? non-empty-array : array) + * @psalm-pure + */ +function array_unique(array $array, int $flags = 0) +{ +} + /** * @psalm-template TKey as array-key * @psalm-template TArray as array diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index af57d042028..bab65c90b32 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -2536,6 +2536,19 @@ function consumeArray(array $_input): void {} consumeArray([makeKey() => null]); ', ], + 'arrayUniquePreservesNonEmptyInput' => [ + 'code' => ' $input */ + function takes_non_empty_array(array $input): void {} + + takes_non_empty_array(array_unique(["test" => (object)[]])); + + /** @param non-empty-array $input */ + function takes_non_empty_int_array(array $input): void {} + + takes_non_empty_int_array(array_unique([(object)[]])); + ', + ], ]; } @@ -2809,6 +2822,24 @@ function merger(array $a, array $b) : array { ', 'error_message' => 'NamedArgumentNotAllowed', ], + 'arrayUniquePreservesEmptyInput' => [ + 'code' => ' $input */ + function takes_non_empty_array(array $input): void {} + + takes_non_empty_array(array_unique([])); + ', + 'error_message' => 'InvalidArgument', + ], + 'arrayUniqueConvertsListToArray' => [ + 'code' => ' $input */ + function takes_non_empty_list(array $input): void {} + + takes_non_empty_list(array_unique([(object)[]])); + ', + 'error_message' => 'ArgumentTypeCoercion', + ], ]; } }