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'
+ ]
];
}
}