diff --git a/psalm-baseline.xml b/psalm-baseline.xml index b8dacf52dee..c8dee018001 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -56,17 +56,17 @@ $source_parts[1] + + + $stmt->cond + + if (AtomicTypeComparator::isContainedBy( if (AtomicTypeComparator::isContainedBy( - - - $pre_conditions[0] - - $context->assigned_var_ids += $switch_scope->new_assigned_var_ids diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php index 79ac5bda923..985d73c3c3b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php @@ -33,8 +33,8 @@ class LoopAnalyzer /** * Checks an array of statements in a loop * - * @param array $stmts - * @param PhpParser\Node\Expr[] $pre_conditions + * @param list $stmts + * @param list $pre_conditions * @param PhpParser\Node\Expr[] $post_expressions * @return false|null */ diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index e66ed4a5c4f..1a657a7391f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -59,6 +59,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $all_int_offsets = true; $all_nonempty_lists = true; $any_nonempty = false; + $all_empty = true; $max_keyed_array_size = 0; @@ -75,17 +76,31 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $unpacking_possibly_empty = false; if ($call_arg->unpack) { if ($type_part instanceof TKeyedArray) { - $unpacked_type_parts = $type_part->getGenericValueType(); - $unpacking_indefinite_number_of_args = $type_part->fallback_params !== null; + if (!$type_part->fallback_params + && $type_part->getMinCount() === $type_part->getMaxCount() + ) { + $unpacked_type_parts = []; + foreach ($type_part->properties as $t) { + $unpacked_type_parts = array_merge( + $unpacked_type_parts, + $t->getAtomicTypes() + ); + } + } else { + $unpacked_type_parts = $type_part + ->getGenericValueType() + ->getAtomicTypes(); + $unpacking_indefinite_number_of_args = true; + } $unpacking_possibly_empty = !$type_part->isNonEmpty(); } elseif ($type_part instanceof TArray) { $unpacked_type_parts = $type_part->type_params[1]; $unpacking_indefinite_number_of_args = true; $unpacking_possibly_empty = !$type_part instanceof TNonEmptyArray; + $unpacked_type_parts = $unpacked_type_parts->getAtomicTypes(); } else { return Type::getArray(); } - $unpacked_type_parts = $unpacked_type_parts->getAtomicTypes(); } else { $unpacked_type_parts = [$type_part]; } @@ -100,6 +115,8 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } if ($unpacked_type_part instanceof TKeyedArray) { + $all_empty = false; + $max_keyed_array_size = max( $max_keyed_array_size, count($unpacked_type_part->properties) @@ -214,6 +231,8 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return Type::getArray(); } + $all_empty = false; + $inner_key_types = array_merge( $inner_key_types, array_values($unpacked_type_part->type_params[0]->getAtomicTypes()) @@ -256,6 +275,10 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return new Union([$objectlike]); } + if ($all_empty) { + return Type::getEmptyArray(); + } + if ($inner_value_type) { if ($all_int_offsets) { if ($any_nonempty) { diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index a8399e9e53e..92e1e9d7de3 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -336,6 +336,8 @@ function array_key_exists($key, array $array) : bool /** * @psalm-pure * + * @no-named-arguments + * * @psalm-template TKey as array-key * @psalm-template TValue * @@ -347,6 +349,22 @@ function array_merge_recursive(array ...$arrays) { } +/** + * @psalm-pure + * + * @no-named-arguments + * + * @psalm-template TKey as array-key + * @psalm-template TValue + * + * @param array ...$arrays + * + * @return array + */ +function array_merge(array ...$arrays) +{ +} + /** * @psalm-pure * diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 3189266a0cb..f82b4bf9788 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -301,6 +301,20 @@ public function merge($a, $b): array '$d===' => "list{string, ..., int|string>}", ], ], + 'arrayMergeEmpty' => [ + 'code' => ' 0]]; + $b = array_merge(...$test); + ', + 'assertions' => [ + '$a===' => 'array', + '$b===' => 'array{test: 0}', + ] + ], 'arrayReplaceIntArrays' => [ 'code' => ' [ 'code' => '> $elements + * @param array> $elements * @return list */ function resolvePossibleFilePaths($elements) : array @@ -2754,6 +2768,20 @@ function merger(array $a, array $b) : array { array_combine(["a", "b"], [1, 2, 3]);', 'error_message' => 'InvalidArgument', ], + 'arrayMergeNoNamed' => [ + 'code' => ' []]; + array_merge(...$map); + ', + 'error_message' => 'NamedArgumentNotAllowed' + ], + 'arrayMergeRecursiveNoNamed' => [ + 'code' => ' []]; + array_merge_recursive(...$map); + ', + 'error_message' => 'NamedArgumentNotAllowed' + ] ]; } }