From a1b24db5e96ca7b4be3e8537293e9c0d3b2bd138 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 30 Mar 2024 15:35:55 +0100 Subject: [PATCH] More fixes --- .../Call/ArrayFunctionArgumentsAnalyzer.php | 149 +++++++++--------- .../Call/FunctionCallReturnTypeFetcher.php | 10 +- .../ArrayCombineReturnTypeProvider.php | 142 +++++++++-------- .../ArraySpliceReturnTypeProvider.php | 42 +++-- 4 files changed, 174 insertions(+), 169 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 1d6a5788b9b..4d54c0d0e65 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -204,100 +204,101 @@ public static function handleAddition( if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg)) && $array_arg_type->hasArray() ) { - $array_type = $array_arg_type->getArray(); + $array_types = $array_arg_type->getArrays(); + $by_ref_type = new Union([$array_types]); - $objectlike_list = null; + foreach ($array_types as $array_type) { + $objectlike_list = null; - if ($array_type instanceof TKeyedArray) { - if ($array_type->is_list) { - $objectlike_list = $array_type; + if ($array_type instanceof TKeyedArray) { + if ($array_type->is_list) { + $objectlike_list = $array_type; + } } - } - $by_ref_type = new Union([$array_type]); - - foreach ($args as $argument_offset => $arg) { - if ($argument_offset === 0) { - continue; - } + foreach ($args as $argument_offset => $arg) { + if ($argument_offset === 0) { + continue; + } - if (ExpressionAnalyzer::analyze( - $statements_analyzer, - $arg->value, - $context, - ) === false) { - return false; - } + if (ExpressionAnalyzer::analyze( + $statements_analyzer, + $arg->value, + $context, + ) === false) { + return false; + } - if ($method_id === 'array_unshift' && $nb_args === 2 && !$unpacked_args) { - $new_offset_type = Type::getInt(false, 0); - } else { - $new_offset_type = Type::getInt(); - } + if ($method_id === 'array_unshift' && $nb_args === 2 && !$unpacked_args) { + $new_offset_type = Type::getInt(false, 0); + } else { + $new_offset_type = Type::getInt(); + } - if (!($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) + if (!($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) || $arg_value_type->hasMixed() - ) { - $by_ref_type = Type::combineUnionTypes( - $by_ref_type, - new Union([new TArray([$new_offset_type, Type::getMixed()])]), - ); - } elseif ($arg->unpack) { - $arg_value_type = $arg_value_type->getBuilder(); - - foreach ($arg_value_type->getAtomicTypes() as $arg_value_atomic_type) { - if ($arg_value_atomic_type instanceof TKeyedArray) { - $was_list = $arg_value_atomic_type->is_list; - - $arg_value_atomic_type = $arg_value_atomic_type->getGenericArrayType(); - - if ($was_list) { - if ($arg_value_atomic_type instanceof TNonEmptyArray) { - $arg_value_atomic_type = Type::getNonEmptyListAtomic( - $arg_value_atomic_type->type_params[1], - ); - } else { - $arg_value_atomic_type = Type::getListAtomic( - $arg_value_atomic_type->type_params[1], - ); + ) { + $by_ref_type = Type::combineUnionTypes( + $by_ref_type, + new Union([new TArray([$new_offset_type, Type::getMixed()])]), + ); + } elseif ($arg->unpack) { + $arg_value_type = $arg_value_type->getBuilder(); + + foreach ($arg_value_type->getAtomicTypes() as $arg_value_atomic_type) { + if ($arg_value_atomic_type instanceof TKeyedArray) { + $was_list = $arg_value_atomic_type->is_list; + + $arg_value_atomic_type = $arg_value_atomic_type->getGenericArrayType(); + + if ($was_list) { + if ($arg_value_atomic_type instanceof TNonEmptyArray) { + $arg_value_atomic_type = Type::getNonEmptyListAtomic( + $arg_value_atomic_type->type_params[1], + ); + } else { + $arg_value_atomic_type = Type::getListAtomic( + $arg_value_atomic_type->type_params[1], + ); + } } - } - $arg_value_type->addType($arg_value_atomic_type); + $arg_value_type->addType($arg_value_atomic_type); + } } - } - $arg_value_type = $arg_value_type->freeze(); + $arg_value_type = $arg_value_type->freeze(); - $by_ref_type = Type::combineUnionTypes( - $by_ref_type, - $arg_value_type, - ); - } else { - if ($objectlike_list) { - $properties = $objectlike_list->properties; - array_unshift($properties, $arg_value_type); - - $by_ref_type = new Union([$objectlike_list->setProperties($properties)]); - } elseif ($array_type instanceof TArray && $array_type->isEmpty()) { - $by_ref_type = new Union([new TKeyedArray([ - $arg_value_type, - ], null, null, true)]); - } else { $by_ref_type = Type::combineUnionTypes( $by_ref_type, - new Union( - [ + $arg_value_type, + ); + } else { + if ($objectlike_list) { + $properties = $objectlike_list->properties; + array_unshift($properties, $arg_value_type); + + $by_ref_type = new Union([$objectlike_list->setProperties($properties)]); + } elseif ($array_type instanceof TArray && $array_type->isEmpty()) { + $by_ref_type = new Union([new TKeyedArray([ + $arg_value_type, + ], null, null, true)]); + } else { + $by_ref_type = Type::combineUnionTypes( + $by_ref_type, + new Union( + [ new TNonEmptyArray( [ $new_offset_type, $arg_value_type, ], ), - ], - ), - null, - true, - ); + ], + ), + null, + true, + ); + } } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php index e9d312c012b..c33fcc34f37 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php @@ -26,7 +26,6 @@ use Psalm\Storage\FunctionLikeStorage; use Psalm\Type; use Psalm\Type\Atomic\ArrayInterface; -use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TCallableKeyedArray; use Psalm\Type\Atomic\TClassString; @@ -429,14 +428,7 @@ private static function getReturnTypeFromCallMapWithArgs( if ($first_arg_type = $statements_analyzer->node_data->getType($first_arg)) { if ($first_arg_type->hasArray()) { - $array_type = $first_arg_type->getArray(); - if ($array_type instanceof TKeyedArray) { - return $array_type->getGenericValueType(); - } - - if ($array_type instanceof TArray) { - return $array_type->type_params[1]; - } + return $first_arg_type->getArrayValueType($codebase); } elseif ($first_arg_type->hasScalarType() && ($second_arg = ($call_args[1]->value ?? null)) && ($second_arg_type = $statements_analyzer->node_data->getType($second_arg)) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php index 9464a67b281..488aeeb0500 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php @@ -47,15 +47,6 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return null; } - $keys = $keys_type->getArray(); - if ($keys instanceof TArray && $keys->isEmptyArray()) { - $keys = []; - } elseif (!$keys instanceof TKeyedArray || $keys->fallback_params) { - return null; - } else { - $keys = $keys->properties; - } - if (!$values_type = $statements_source->node_data->getType($call_args[1]->value)) { return null; } @@ -63,69 +54,92 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return null; } - $values = $values_type->getArray(); - if ($values instanceof TArray && $values->isEmptyArray()) { - $values = []; - } elseif (!$values instanceof TKeyedArray || $values->fallback_params) { - return null; - } else { - $values = $values->properties; - } - - - $keys_array = []; - $is_list = true; - $prev_key = -1; - foreach ($keys as $key) { - if ($key->possibly_undefined) { - return null; - } - if ($key->isSingleIntLiteral()) { - $key = $key->getSingleIntLiteral()->value; - $keys_array []= $key; - if ($is_list && $key-1 !== $prev_key) { - $is_list = false; - } - $prev_key = $key; - } elseif ($key->isSingleStringLiteral()) { - $keys_array []= $key->getSingleStringLiteral()->value; - $is_list = false; + $has_fallback = false; + $result = []; + foreach ($values_type->getArrays() as $values) { + if ($values instanceof TArray && $values->isEmptyArray()) { + $values = []; + } elseif (!$values instanceof TKeyedArray || $values->fallback_params) { + $has_fallback = true; + continue; } else { - return null; + $values = $values->properties; + foreach ($values as $value) { + if ($value->possibly_undefined) { + $has_fallback = true; + continue; + } + } } - } - foreach ($values as $value) { - if ($value->possibly_undefined) { - return null; - } - } + foreach ($keys_type->getArrays() as $keys) { + $keys_array = []; + + if ($keys instanceof TArray && $keys->isEmptyArray()) { + $keys = []; + } elseif (!$keys instanceof TKeyedArray || $keys->fallback_params) { + $has_fallback = true; + continue; + } else { + $keys = $keys->properties; + $is_list = true; + $prev_key = -1; + + foreach ($keys as $key) { + if ($key->possibly_undefined) { + $has_fallback = true; + continue; + } + if ($key->isSingleIntLiteral()) { + $key = $key->getSingleIntLiteral()->value; + $keys_array []= $key; + if ($is_list && $key-1 !== $prev_key) { + $is_list = false; + } + $prev_key = $key; + } elseif ($key->isSingleStringLiteral()) { + $keys_array []= $key->getSingleStringLiteral()->value; + $is_list = false; + } else { + $has_fallback = true; + continue; + } + } + } - if (count($keys_array) !== count($values)) { - IssueBuffer::maybeAdd( - new InvalidArgument( - 'The keys array ' . $keys_type->getId() . ' must have exactly the same ' - . 'number of elements as the values array ' + if (count($keys_array) !== count($values)) { + IssueBuffer::maybeAdd( + new InvalidArgument( + 'The keys array ' . $keys_type->getId() . ' must have exactly the same ' + . 'number of elements as the values array ' . $values_type->getId(), - $event->getCodeLocation(), - 'array_combine', - ), - $statements_source->getSuppressedIssues(), - ); - return $statements_source->getCodebase()->analysis_php_version_id >= 8_00_00 - ? Type::getNever() - : Type::getFalse(); - } + $event->getCodeLocation(), + 'array_combine', + ), + $statements_source->getSuppressedIssues(), + ); + return $statements_source->getCodebase()->analysis_php_version_id >= 8_00_00 + ? Type::getNever() + : Type::getFalse(); + } + + $temp = array_combine( + $keys_array, + $values, + ); - $result = array_combine( - $keys_array, - $values, - ); + if ($temp) { + $result []= Type::getEmptyArrayAtomic(); + } else { + $result []= new TKeyedArray($result, null, null, $is_list); + } + } + } - if (!$result) { - return Type::getEmptyArray(); + if ($has_fallback) { + $result []= new TArray([Type::getArrayKey(), Type::getMixed()]); } - return new Union([new TKeyedArray($result, null, null, $is_list)]); + return new Union($result); } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php index 5602839995a..0a7a5a46e85 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php @@ -8,7 +8,6 @@ use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\Type; -use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Union; @@ -35,33 +34,32 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $first_arg = $call_args[0]->value ?? null; - $array_type = $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 (!$array_type) { + if (!$first_arg + || !($first_arg_type = $statements_source->node_data->getType($first_arg)) + || !$first_arg_type->hasArray() + ) { return Type::getArray(); } - if ($array_type instanceof TKeyedArray) { - $array_type = $array_type->getGenericArrayType(); - } + // TODO improve this logic + $results = []; + foreach ($first_arg_type->getArrays() as $array_type) { + if ($array_type instanceof TKeyedArray) { + $array_type = $array_type->getGenericArrayType(); + } - if (!$array_type->type_params[0]->hasString()) { - if ($array_type->type_params[1]->isString()) { - $array_type = Type::getListAtomic(Type::getString()); - } elseif ($array_type->type_params[1]->isInt()) { - $array_type = Type::getListAtomic(Type::getInt()); - } else { - $array_type = Type::getListAtomic(Type::getMixed()); + if (!$array_type->type_params[0]->hasString()) { + if ($array_type->type_params[1]->isString()) { + $array_type = Type::getListAtomic(Type::getString()); + } elseif ($array_type->type_params[1]->isInt()) { + $array_type = Type::getListAtomic(Type::getInt()); + } else { + $array_type = Type::getListAtomic(Type::getMixed()); + } } + $results []= $array_type; } - return new Union([$array_type]); + return new Union($results); } }