From 761451fddf18634894cc4ebd9e0b827f270a06a8 Mon Sep 17 00:00:00 2001 From: Takuya Aramaki Date: Mon, 27 Oct 2025 02:11:50 +0900 Subject: [PATCH 1/2] Refactor `FunctionCallParametersCheck` `$parameterCount` is always >0 when `$isNativelyVariadic` is true --- src/Rules/FunctionCallParametersCheck.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 5c3821aa4f..886df0d08d 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -603,12 +603,7 @@ private function processArguments( } else { $namedArgumentAlreadyOccurred = true; - $parametersCount = count($parameters); - if ( - !$isNativelyVariadic - || $parametersCount <= 0 - || $isBuiltin - ) { + if (!$isNativelyVariadic || $isBuiltin) { $errors[] = RuleErrorBuilder::message(sprintf($unknownParameterMessage, $argumentName)) ->identifier('argument.unknown') ->line($argumentLine) @@ -617,6 +612,7 @@ private function processArguments( continue; } + $parametersCount = count($parameters); $parameter = $parameters[$parametersCount - 1]; $originalParameter = $originalParameters[$parametersCount - 1]; } From cf2127522a68104bf036caca40fd502bc7cf3f82 Mon Sep 17 00:00:00 2001 From: Takuya Aramaki Date: Fri, 31 Oct 2025 00:33:36 +0900 Subject: [PATCH 2/2] Detect duplicated args passed to a variadic parameter Closes phpstan/phpstan#13710 --- src/Rules/FunctionCallParametersCheck.php | 9 +++++++++ .../Rules/Functions/CallToFunctionParametersRuleTest.php | 4 ++++ tests/PHPStan/Rules/Functions/data/named-arguments.php | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 886df0d08d..955dad6383 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -578,6 +578,7 @@ private function processArguments( $newArguments = []; $namedArgumentAlreadyOccurred = false; + $namedArgumentsForVariadicParameter = []; foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine]) { if ($argumentName === null) { if (!isset($parameters[$i])) { @@ -615,6 +616,14 @@ private function processArguments( $parametersCount = count($parameters); $parameter = $parameters[$parametersCount - 1]; $originalParameter = $originalParameters[$parametersCount - 1]; + + if (isset($namedArgumentsForVariadicParameter[$argumentName])) { + $errors[] = RuleErrorBuilder::message(sprintf('Named parameter $%s overwrites previous argument.', $argumentName)) + ->identifier('argument.duplicate') + ->line($argumentLine) + ->build(); + } + $namedArgumentsForVariadicParameter[$argumentName] = true; } if ($namedArgumentAlreadyOccurred && $argumentName === null && !$unpack) { diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index b179c1ff0d..87f6d20496 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -512,6 +512,10 @@ public function testNamedArguments(): void 'Unknown parameter $a in call to function array_merge.', 14, ], + [ + 'Named parameter $var overwrites previous argument.', + 19, + ], ]; require_once __DIR__ . '/data/named-arguments-define.php'; diff --git a/tests/PHPStan/Rules/Functions/data/named-arguments.php b/tests/PHPStan/Rules/Functions/data/named-arguments.php index 3e349530b4..4cda8071d1 100644 --- a/tests/PHPStan/Rules/Functions/data/named-arguments.php +++ b/tests/PHPStan/Rules/Functions/data/named-arguments.php @@ -13,3 +13,8 @@ function baz(): void variadicFunction(...['a' => ['b', 'c']]); // works - userland array_merge(...['a' => ['b', 'c']]); // doesn't work - internal } + +function bug13710(): void +{ + variadicFunction(var: 1, var: 2); +}