diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index f7143c42d..92eccd229 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -406,6 +406,20 @@ class ArrayFunctionArgumentsAnalyzer */ $replacement_array_type = $replacement_arg_type->getAtomicTypes()['array']; + if ($replacement_array_type instanceof ObjectLike) { + $was_list = $replacement_array_type->is_list; + + $replacement_array_type = $replacement_array_type->getGenericArrayType(); + + if ($was_list) { + if ($replacement_array_type instanceof TNonEmptyArray) { + $replacement_array_type = new TNonEmptyList($replacement_array_type->type_params[1]); + } else { + $replacement_array_type = new TList($replacement_array_type->type_params[1]); + } + } + } + $by_ref_type = TypeCombination::combineTypes([$array_type, $replacement_array_type]); AssignmentAnalyzer::assignByRefParam( @@ -465,7 +479,13 @@ class ArrayFunctionArgumentsAnalyzer \array_shift($array_properties); if (!$array_properties) { - $array_properties = [$array_atomic_type->previous_value_type ?: Type::getMixed()]; + $array_properties = [ + $array_atomic_type->previous_value_type + ? clone $array_atomic_type->previous_value_type + : Type::getMixed() + ]; + + $array_properties[0]->possibly_undefined = true; } $array_atomic_type->properties = $array_properties; diff --git a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php index 6f8be86a3..a2b405081 100644 --- a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -32,8 +32,6 @@ class ArrayTypeComparator ) : bool { $all_types_contain = true; - $prior_input_type_part = $input_type_part; - if ($container_type_part instanceof ObjectLike && $input_type_part instanceof TArray ) { @@ -215,17 +213,6 @@ class ArrayTypeComparator if ($container_type_part instanceof Type\Atomic\TNonEmptyArray && !$input_type_part instanceof Type\Atomic\TNonEmptyArray - && !($input_type_part instanceof ObjectLike - && ($input_type_part->sealed - || $input_type_part->previous_value_type - || \array_filter( - $input_type_part->properties, - function ($prop_type) { - return !$prop_type->possibly_undefined; - } - ) - ) - ) ) { if ($all_types_contain && $atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; diff --git a/src/Psalm/Internal/Type/TypeCombination.php b/src/Psalm/Internal/Type/TypeCombination.php index 54e2bf2f1..c8647621f 100644 --- a/src/Psalm/Internal/Type/TypeCombination.php +++ b/src/Psalm/Internal/Type/TypeCombination.php @@ -455,6 +455,7 @@ class TypeCombination $array_type = new ObjectLike([$generic_type_params[1]]); $array_type->previous_key_type = Type::getInt(); $array_type->previous_value_type = $combination->array_type_params[1]; + $array_type->is_list = true; } else { $array_type = new TNonEmptyList($generic_type_params[1]); diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 2e5fa088a..50edef41b 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -292,6 +292,17 @@ class ArrayFunctionCallTest extends TestCase return 0; }', ], + 'arrayShiftFunkyObjectLikeList' => [ + '|array{null} $arr + * @return array + */ + function foo(array $arr) { + array_shift($arr); + return $arr; + }' + ], 'arrayPopNonEmptyAfterCountEqualsOne' => [ ' */ @@ -1013,18 +1024,23 @@ class ArrayFunctionCallTest extends TestCase 'assertions' => [], 'error_levels' => ['MissingClosureReturnType', 'MixedAssignment'], ], - 'arraySplice' => [ + 'arraySpliceArray' => [ ' [ '$a' => 'non-empty-list', '$b' => 'array{string, string, string}', '$c' => 'array{int, int, int}', + ], + ], + 'arraySpliceReturn' => [ + ' [ '$e' => 'array' ], ],