mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Add proper handling of unpacked arguments with string keys (#5446)
* Add proper handling of unpacked arguments with string keys * Fix undefined array key error * Fix missed named arguments handling * Fix false-positive on variadic parameter * Add tests
This commit is contained in:
parent
4eca6acebd
commit
fb94db9b1f
@ -639,9 +639,72 @@ class ArgumentsAnalyzer
|
||||
$matched_args = [];
|
||||
|
||||
foreach ($args as $argument_offset => $arg) {
|
||||
if ($arg->unpack && $function_param_count > $argument_offset) {
|
||||
for ($i = $argument_offset; $i < $function_param_count; $i++) {
|
||||
$arg_function_params[$argument_offset][] = $function_params[$i];
|
||||
if ($arg->unpack) {
|
||||
if ($function_param_count > $argument_offset) {
|
||||
for ($i = $argument_offset; $i < $function_param_count; $i++) {
|
||||
$arg_function_params[$argument_offset][] = $function_params[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (($arg_value_type = $statements_analyzer->node_data->getType($arg->value))
|
||||
&& $arg_value_type->hasArray()) {
|
||||
/**
|
||||
* @psalm-suppress PossiblyUndefinedStringArrayOffset
|
||||
* @var TArray|TList|TKeyedArray
|
||||
*/
|
||||
$array_type = $arg_value_type->getAtomicTypes()['array'];
|
||||
|
||||
if ($array_type instanceof TKeyedArray) {
|
||||
$key_types = $array_type->getGenericArrayType()->getChildNodes()[0]->getChildNodes();
|
||||
|
||||
foreach ($key_types as $key_type) {
|
||||
if (!$key_type instanceof Type\Atomic\TLiteralString
|
||||
|| ($function_storage && !$function_storage->allow_named_arg_calls)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$param_found = false;
|
||||
|
||||
foreach ($function_params as $candidate_param) {
|
||||
if ($candidate_param->name === $key_type->value || $candidate_param->is_variadic) {
|
||||
if ($candidate_param->name === $key_type->value) {
|
||||
if (isset($matched_args[$candidate_param->name])) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidNamedArgument(
|
||||
'Parameter $' . $key_type->value . ' has already been used in '
|
||||
. ($cased_method_id ?: $method_id),
|
||||
new CodeLocation($statements_analyzer, $arg),
|
||||
(string)$method_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
$matched_args[$candidate_param->name] = true;
|
||||
}
|
||||
|
||||
$param_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$param_found) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidNamedArgument(
|
||||
'Parameter $' . $key_type->value . ' does not exist on function '
|
||||
. ($cased_method_id ?: $method_id),
|
||||
new CodeLocation($statements_analyzer, $arg),
|
||||
(string)$method_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($arg->name && (!$function_storage || $function_storage->allow_named_arg_calls)) {
|
||||
foreach ($function_params as $candidate_param) {
|
||||
|
@ -268,6 +268,15 @@ class ArgTest extends TestCase
|
||||
[],
|
||||
'8.0'
|
||||
],
|
||||
'useUnpackedNamedVariadicArguments' => [
|
||||
'<?php
|
||||
function takesArguments(int ...$args) : void {}
|
||||
|
||||
takesArguments(...["age" => 5]);',
|
||||
[],
|
||||
[],
|
||||
'8.0'
|
||||
],
|
||||
'variadicArgsOptional' => [
|
||||
'<?php
|
||||
bar(...["aaaaa"]);
|
||||
@ -378,6 +387,27 @@ class ArgTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'InvalidNamedArgument'
|
||||
],
|
||||
'useUnpackedInvalidNamedArgument' => [
|
||||
'<?php
|
||||
class CustomerData {
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $email,
|
||||
public int $age,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{aage: int, name: string, email: string} $input
|
||||
*/
|
||||
function foo(array $input) : CustomerData {
|
||||
return new CustomerData(...$input);
|
||||
}',
|
||||
'error_message' => 'InvalidNamedArgument',
|
||||
[],
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
'noNamedArgsMethod' => [
|
||||
'<?php
|
||||
class CustomerData
|
||||
@ -504,6 +534,18 @@ class ArgTest extends TestCase
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
'overwriteOrderedWithUnpackedNamedParam' => [
|
||||
'<?php
|
||||
function test(int $param, int $param2): void {
|
||||
echo $param + $param2;
|
||||
}
|
||||
|
||||
test(1, ...["param" => 2]);',
|
||||
'error_message' => 'InvalidNamedArgument',
|
||||
[],
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user