diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index ac083fb1bfa..2503dbd60a2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -646,8 +646,20 @@ public static function checkArgumentsMatch( $arg_function_params = []; $matched_args = []; + $named_args_was_used = false; foreach ($args as $argument_offset => $arg) { + if ($named_args_was_used && !$arg->name) { + IssueBuffer::maybeAdd( + new InvalidNamedArgument( + 'Cannot use positional argument after named argument', + new CodeLocation($statements_analyzer, $arg), + (string)$method_id + ), + $statements_analyzer->getSuppressedIssues() + ); + } + if ($arg->unpack) { if ($function_param_count > $argument_offset) { for ($i = $argument_offset; $i < $function_param_count; $i++) { @@ -712,6 +724,8 @@ public static function checkArgumentsMatch( } } } elseif ($arg->name && (!$function_storage || $function_storage->allow_named_arg_calls)) { + $named_args_was_used = true; + foreach ($function_params as $candidate_param) { if ($candidate_param->name === $arg->name->name || $candidate_param->is_variadic) { if ($candidate_param->name === $arg->name->name) { diff --git a/tests/ArgTest.php b/tests/ArgTest.php index 623ce95e977..259d7cb2f57 100644 --- a/tests/ArgTest.php +++ b/tests/ArgTest.php @@ -406,6 +406,19 @@ function foo(array $input) : CustomerData { }', 'error_message' => 'InvalidNamedArgument' ], + 'usePositionalArgAfterNamed' => [ + ' 'InvalidNamedArgument' + ], 'useUnpackedInvalidNamedArgument' => [ '