1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00

4.x - Support named arguments

Ref #4089
This commit is contained in:
Matt Brown 2020-10-02 20:27:01 -04:00 committed by Daniil Gentili
parent 510f1e75cd
commit cb5630d156
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
4 changed files with 130 additions and 10 deletions

View File

@ -410,7 +410,11 @@ class ArgumentAnalyzer
$unpacked_atomic_array = $arg_type->getAtomicTypes()['array'];
if ($unpacked_atomic_array instanceof Type\Atomic\TKeyedArray) {
if ($unpacked_atomic_array->is_list
if ($codebase->php_major_version >= 8
&& isset($unpacked_atomic_array->properties[$function_param->name])
) {
$arg_type = clone $unpacked_atomic_array->properties[$function_param->name];
} elseif ($unpacked_atomic_array->is_list
&& isset($unpacked_atomic_array->properties[$argument_offset])
) {
$arg_type = clone $unpacked_atomic_array->properties[$argument_offset];

View File

@ -18,6 +18,7 @@ use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\InvalidPassByReference;
use Psalm\Issue\InvalidNamedArgument;
use Psalm\Issue\PossiblyUndefinedVariable;
use Psalm\Issue\TooFewArguments;
use Psalm\Issue\TooManyArguments;
@ -106,9 +107,20 @@ class ArgumentsAnalyzer
continue;
}
$param = $argument_offset < count($function_params)
? $function_params[$argument_offset]
: ($last_param && $last_param->is_variadic ? $last_param : null);
$param = null;
if ($arg->name) {
foreach ($function_params as $candidate_param) {
if ($candidate_param->name === $arg->name->name) {
$param = $candidate_param;
break;
}
}
} elseif ($argument_offset < count($function_params)) {
$param = $function_params[$argument_offset];
} elseif ($last_param && $last_param->is_variadic) {
$param = $last_param;
}
$by_ref = $param && $param->by_ref;
@ -518,9 +530,20 @@ class ArgumentsAnalyzer
}
foreach ($args as $argument_offset => $arg) {
$function_param = count($function_params) > $argument_offset
? $function_params[$argument_offset]
: ($last_param && $last_param->is_variadic ? $last_param : null);
$function_param = null;
if ($arg->name) {
foreach ($function_params as $candidate_param) {
if ($candidate_param->name === $arg->name->name) {
$function_param = $candidate_param;
break;
}
}
} elseif ($argument_offset < count($function_params)) {
$function_param = $function_params[$argument_offset];
} elseif ($last_param && $last_param->is_variadic) {
$function_param = $last_param;
}
if (!$function_param
|| !$function_param->type
@ -595,9 +618,34 @@ class ArgumentsAnalyzer
}
foreach ($args as $argument_offset => $arg) {
$function_param = $function_param_count > $argument_offset
? $function_params[$argument_offset]
: ($last_param && $last_param->is_variadic ? $last_param : null);
$function_param = null;
if ($arg->name) {
foreach ($function_params as $candidate_param) {
if ($candidate_param->name === $arg->name->name) {
$function_param = $candidate_param;
break;
}
}
if (!$function_param) {
if (IssueBuffer::accepts(
new InvalidNamedArgument(
'Parameter $' . $arg->name->name . ' does not exist on function '
. ($cased_method_id ?: $method_id),
new CodeLocation($statements_analyzer, $arg->name),
(string) $method_id
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
} elseif ($function_param_count > $argument_offset) {
$function_param = $function_params[$argument_offset];
} elseif ($last_param && $last_param->is_variadic) {
$function_param = $last_param;
}
if ($function_param
&& $function_param->by_ref

View File

@ -0,0 +1,8 @@
<?php
namespace Psalm\Issue;
class InvalidNamedArgument extends ArgumentIssue
{
public const ERROR_LEVEL = 6;
public const SHORTCODE = 238;
}

View File

@ -121,6 +121,44 @@ class ArgTest extends TestCase
return intval(...$args);
}',
],
'useNamedArguments' => [
'<?php
class CustomerData {
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}
/**
* @param array{age: int, name: string, email: string} $input
*/
function foo(array $input) : CustomerData {
return new CustomerData(
age: $input["age"],
name: $input["name"],
email: $input["email"],
);
}'
],
'useNamedArgumentsSimple' => [
'<?php
function takesArguments(string $name, int $age) : void {}
takesArguments(name: "hello", age: 5);
takesArguments(age: 5, name: "hello");'
],
'useNamedArgumentsSpread' => [
'<?php
function takesArguments(string $name, int $age) : void {}
$args = ["name" => "hello", "age" => 5];
takesArguments(...$args);',
[],
[],
'8.0'
],
];
}
@ -187,6 +225,28 @@ class ArgTest extends TestCase
}',
'error_message' => 'PossiblyNullArgument'
],
'useInvalidNamedArgument' => [
'<?php
class CustomerData {
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}
/**
* @param array{age: int, name: string, email: string} $input
*/
function foo(array $input) : CustomerData {
return new CustomerData(
aage: $input["age"],
name: $input["name"],
email: $input["email"],
);
}',
'error_message' => 'InvalidNamedArgument'
],
];
}
}