diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 4e0d86d0e..4321c8a43 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -622,6 +622,7 @@ class ArgumentsAnalyzer } $arg_function_params = []; + $matched_args = []; foreach ($args as $argument_offset => $arg) { if ($arg->unpack && $function_param_count > $argument_offset) { @@ -631,6 +632,24 @@ class ArgumentsAnalyzer } elseif ($arg->name && (!$function_storage || $function_storage->allow_named_arg_calls)) { foreach ($function_params as $candidate_param) { if ($candidate_param->name === $arg->name->name || $candidate_param->is_variadic) { + if ($candidate_param->name === $arg->name->name) { + if (isset($matched_args[$candidate_param->name])) { + if (IssueBuffer::accepts( + new InvalidNamedArgument( + 'Parameter $' . $arg->name->name . ' has already been used in ' + . ($cased_method_id ?: $method_id), + new CodeLocation($statements_analyzer, $arg->name), + (string) $method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + + $matched_args[$candidate_param->name] = true; + } + $arg_function_params[$argument_offset] = [$candidate_param]; break; } @@ -651,8 +670,10 @@ class ArgumentsAnalyzer } } elseif ($function_param_count > $argument_offset) { $arg_function_params[$argument_offset] = [$function_params[$argument_offset]]; + $matched_args[$function_params[$argument_offset]->name] = true; } elseif ($last_param && $last_param->is_variadic) { $arg_function_params[$argument_offset] = [$last_param]; + $matched_args[$last_param->name] = true; } } diff --git a/tests/ArgTest.php b/tests/ArgTest.php index 1b2613b70..ed8836e8e 100644 --- a/tests/ArgTest.php +++ b/tests/ArgTest.php @@ -480,6 +480,30 @@ class ArgTest extends TestCase echo $b;', 'error_message' => 'PossiblyUndefinedGlobalVariable', ], + 'overwriteNamedParam' => [ + ' 'InvalidNamedArgument', + [], + false, + '8.0' + ], + 'overwriteOrderedNamedParam' => [ + ' 'InvalidNamedArgument', + [], + false, + '8.0' + ], ]; } }