mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 13:51:54 +01:00
Merge pull request #8924 from danog/array_merge
More array_merge improvements
This commit is contained in:
commit
9f5e4f58b2
@ -56,17 +56,17 @@
|
|||||||
<code>$source_parts[1]</code>
|
<code>$source_parts[1]</code>
|
||||||
</PossiblyUndefinedIntArrayOffset>
|
</PossiblyUndefinedIntArrayOffset>
|
||||||
</file>
|
</file>
|
||||||
|
<file src="src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php">
|
||||||
|
<ArgumentTypeCoercion occurrences="1">
|
||||||
|
<code>$stmt->cond</code>
|
||||||
|
</ArgumentTypeCoercion>
|
||||||
|
</file>
|
||||||
<file src="src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php">
|
<file src="src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php">
|
||||||
<ConflictingReferenceConstraint occurrences="2">
|
<ConflictingReferenceConstraint occurrences="2">
|
||||||
<code>if (AtomicTypeComparator::isContainedBy(</code>
|
<code>if (AtomicTypeComparator::isContainedBy(</code>
|
||||||
<code>if (AtomicTypeComparator::isContainedBy(</code>
|
<code>if (AtomicTypeComparator::isContainedBy(</code>
|
||||||
</ConflictingReferenceConstraint>
|
</ConflictingReferenceConstraint>
|
||||||
</file>
|
</file>
|
||||||
<file src="src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php">
|
|
||||||
<PossiblyUndefinedIntArrayOffset occurrences="1">
|
|
||||||
<code>$pre_conditions[0]</code>
|
|
||||||
</PossiblyUndefinedIntArrayOffset>
|
|
||||||
</file>
|
|
||||||
<file src="src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php">
|
<file src="src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php">
|
||||||
<InvalidPropertyAssignmentValue occurrences="1">
|
<InvalidPropertyAssignmentValue occurrences="1">
|
||||||
<code>$context->assigned_var_ids += $switch_scope->new_assigned_var_ids</code>
|
<code>$context->assigned_var_ids += $switch_scope->new_assigned_var_ids</code>
|
||||||
|
@ -33,8 +33,8 @@ class LoopAnalyzer
|
|||||||
/**
|
/**
|
||||||
* Checks an array of statements in a loop
|
* Checks an array of statements in a loop
|
||||||
*
|
*
|
||||||
* @param array<PhpParser\Node\Stmt> $stmts
|
* @param list<PhpParser\Node\Stmt> $stmts
|
||||||
* @param PhpParser\Node\Expr[] $pre_conditions
|
* @param list<PhpParser\Node\Expr> $pre_conditions
|
||||||
* @param PhpParser\Node\Expr[] $post_expressions
|
* @param PhpParser\Node\Expr[] $post_expressions
|
||||||
* @return false|null
|
* @return false|null
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +59,7 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
|
|||||||
$all_int_offsets = true;
|
$all_int_offsets = true;
|
||||||
$all_nonempty_lists = true;
|
$all_nonempty_lists = true;
|
||||||
$any_nonempty = false;
|
$any_nonempty = false;
|
||||||
|
$all_empty = true;
|
||||||
|
|
||||||
$max_keyed_array_size = 0;
|
$max_keyed_array_size = 0;
|
||||||
|
|
||||||
@ -75,17 +76,31 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
|
|||||||
$unpacking_possibly_empty = false;
|
$unpacking_possibly_empty = false;
|
||||||
if ($call_arg->unpack) {
|
if ($call_arg->unpack) {
|
||||||
if ($type_part instanceof TKeyedArray) {
|
if ($type_part instanceof TKeyedArray) {
|
||||||
$unpacked_type_parts = $type_part->getGenericValueType();
|
if (!$type_part->fallback_params
|
||||||
$unpacking_indefinite_number_of_args = $type_part->fallback_params !== null;
|
&& $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();
|
$unpacking_possibly_empty = !$type_part->isNonEmpty();
|
||||||
} elseif ($type_part instanceof TArray) {
|
} elseif ($type_part instanceof TArray) {
|
||||||
$unpacked_type_parts = $type_part->type_params[1];
|
$unpacked_type_parts = $type_part->type_params[1];
|
||||||
$unpacking_indefinite_number_of_args = true;
|
$unpacking_indefinite_number_of_args = true;
|
||||||
$unpacking_possibly_empty = !$type_part instanceof TNonEmptyArray;
|
$unpacking_possibly_empty = !$type_part instanceof TNonEmptyArray;
|
||||||
|
$unpacked_type_parts = $unpacked_type_parts->getAtomicTypes();
|
||||||
} else {
|
} else {
|
||||||
return Type::getArray();
|
return Type::getArray();
|
||||||
}
|
}
|
||||||
$unpacked_type_parts = $unpacked_type_parts->getAtomicTypes();
|
|
||||||
} else {
|
} else {
|
||||||
$unpacked_type_parts = [$type_part];
|
$unpacked_type_parts = [$type_part];
|
||||||
}
|
}
|
||||||
@ -100,6 +115,8 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($unpacked_type_part instanceof TKeyedArray) {
|
if ($unpacked_type_part instanceof TKeyedArray) {
|
||||||
|
$all_empty = false;
|
||||||
|
|
||||||
$max_keyed_array_size = max(
|
$max_keyed_array_size = max(
|
||||||
$max_keyed_array_size,
|
$max_keyed_array_size,
|
||||||
count($unpacked_type_part->properties)
|
count($unpacked_type_part->properties)
|
||||||
@ -214,6 +231,8 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
|
|||||||
return Type::getArray();
|
return Type::getArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$all_empty = false;
|
||||||
|
|
||||||
$inner_key_types = array_merge(
|
$inner_key_types = array_merge(
|
||||||
$inner_key_types,
|
$inner_key_types,
|
||||||
array_values($unpacked_type_part->type_params[0]->getAtomicTypes())
|
array_values($unpacked_type_part->type_params[0]->getAtomicTypes())
|
||||||
@ -256,6 +275,10 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
|
|||||||
return new Union([$objectlike]);
|
return new Union([$objectlike]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($all_empty) {
|
||||||
|
return Type::getEmptyArray();
|
||||||
|
}
|
||||||
|
|
||||||
if ($inner_value_type) {
|
if ($inner_value_type) {
|
||||||
if ($all_int_offsets) {
|
if ($all_int_offsets) {
|
||||||
if ($any_nonempty) {
|
if ($any_nonempty) {
|
||||||
|
@ -336,6 +336,8 @@ function array_key_exists($key, array $array) : bool
|
|||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @psalm-pure
|
||||||
*
|
*
|
||||||
|
* @no-named-arguments
|
||||||
|
*
|
||||||
* @psalm-template TKey as array-key
|
* @psalm-template TKey as array-key
|
||||||
* @psalm-template TValue
|
* @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<TKey, TValue> ...$arrays
|
||||||
|
*
|
||||||
|
* @return array<TKey, TValue>
|
||||||
|
*/
|
||||||
|
function array_merge(array ...$arrays)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @psalm-pure
|
* @psalm-pure
|
||||||
*
|
*
|
||||||
|
@ -301,6 +301,20 @@ class ArrayFunctionCallTest extends TestCase
|
|||||||
'$d===' => "list{string, ...<int<0, max>, int|string>}",
|
'$d===' => "list{string, ...<int<0, max>, int|string>}",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'arrayMergeEmpty' => [
|
||||||
|
'code' => '<?php
|
||||||
|
|
||||||
|
$test = [[]];
|
||||||
|
$a = array_merge(...$test);
|
||||||
|
|
||||||
|
$test = [[], ["test" => 0]];
|
||||||
|
$b = array_merge(...$test);
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$a===' => 'array<never, never>',
|
||||||
|
'$b===' => 'array{test: 0}',
|
||||||
|
]
|
||||||
|
],
|
||||||
'arrayReplaceIntArrays' => [
|
'arrayReplaceIntArrays' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
$d = array_replace(["a", "b", "c", "d"], [1, 2, 3]);',
|
$d = array_replace(["a", "b", "c", "d"], [1, 2, 3]);',
|
||||||
@ -2119,7 +2133,7 @@ class ArrayFunctionCallTest extends TestCase
|
|||||||
'arrayMapWithEmptyArrayReturn' => [
|
'arrayMapWithEmptyArrayReturn' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
/**
|
/**
|
||||||
* @param array<array<string>> $elements
|
* @param array<int, array<string>> $elements
|
||||||
* @return list<string>
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
function resolvePossibleFilePaths($elements) : array
|
function resolvePossibleFilePaths($elements) : array
|
||||||
@ -2754,6 +2768,20 @@ class ArrayFunctionCallTest extends TestCase
|
|||||||
array_combine(["a", "b"], [1, 2, 3]);',
|
array_combine(["a", "b"], [1, 2, 3]);',
|
||||||
'error_message' => 'InvalidArgument',
|
'error_message' => 'InvalidArgument',
|
||||||
],
|
],
|
||||||
|
'arrayMergeNoNamed' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$map = ["a" => []];
|
||||||
|
array_merge(...$map);
|
||||||
|
',
|
||||||
|
'error_message' => 'NamedArgumentNotAllowed'
|
||||||
|
],
|
||||||
|
'arrayMergeRecursiveNoNamed' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$map = ["a" => []];
|
||||||
|
array_merge_recursive(...$map);
|
||||||
|
',
|
||||||
|
'error_message' => 'NamedArgumentNotAllowed'
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user