1
0
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:
Sergey Yakimov 2021-03-22 15:08:05 +02:00 committed by GitHub
parent 4eca6acebd
commit fb94db9b1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 3 deletions

View File

@ -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) {

View File

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