From f24d68def97d3b752e13f7eb064c3ba3a3c5ffec Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Mon, 2 Nov 2026 11:53:14 +0000 Subject: [PATCH 1/3] Make named arguments passed to @no-named-arguments callables fixable The fix passes the arguments positionally in parameter order and fills skipped optional parameters with their default values. Amp-Thread-ID: https://ampcode.com/threads/T-01a19c14-ede5-7153-b7e4-e784ff94be68 --- src/Rules/FunctionCallParametersCheck.php | 113 +++++++++++++++++- .../CallToFunctionParametersRuleTest.php | 6 + .../Functions/data/fix-no-named-arguments.php | 52 ++++++++ .../data/fix-no-named-arguments.php.fixed | 56 +++++++++ 4 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php create mode 100644 tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index d5c6cab8daf..0d7d444e89b 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules; +use Closure; use PhpParser\Node; use PhpParser\Node\Expr; use PHPStan\Analyser\CollectedDataEmitter; @@ -43,6 +44,8 @@ use function count; use function implode; use function in_array; +use function is_bool; +use function is_float; use function is_int; use function is_string; use function lcfirst; @@ -336,6 +339,7 @@ public function check( return $errors; } + $positionalArgumentsFix = null; foreach ($argumentsWithParameters as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName, $argumentLine, $parameter, $originalParameter]) { if ($this->checkArgumentTypes && $unpack) { $iterableTypeResult = $this->ruleLevelHelper->findTypeToCheck( @@ -375,10 +379,15 @@ public function check( if (!$acceptsNamedArguments->yes()) { if ($argumentName !== null) { - $errors[] = RuleErrorBuilder::message(sprintf($namedArgumentMessage, sprintf('named argument $%s', $argumentName))) + $errorBuilder = RuleErrorBuilder::message(sprintf($namedArgumentMessage, sprintf('named argument $%s', $argumentName))) ->identifier('argument.named') - ->line($argumentLine) - ->build(); + ->line($argumentLine); + $positionalArgumentsFix ??= $this->createPositionalArgumentsFix($parametersAcceptor, $funcCall); + if ($positionalArgumentsFix !== false) { + // every named argument of the call reports the same fix, the identical changes are applied once + $errorBuilder->fixNode($funcCall, $positionalArgumentsFix); + } + $errors[] = $errorBuilder->build(); } elseif ($unpack) { $unpackedArrayType = $scope->getType($argumentValue); $hasStringKey = $unpackedArrayType->getIterableKeyType()->isString(); @@ -892,6 +901,104 @@ private function resolveConstantReflections(Expr $expr, Scope $scope): ?array return null; } + /** + * Rewrites the call to pass every argument positionally, skipped optional parameters get their default values. + * + * @return (Closure(Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_): (Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_))|false + */ + private function createPositionalArgumentsFix( + ParametersAcceptor $parametersAcceptor, + Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall, + ): Closure|false + { + if ((bool) $funcCall->getAttribute('isAttribute', false)) { + // the call was created from an attribute, it is not in the file + return false; + } + + $parameters = $parametersAcceptor->getParameters(); + $parameterPositions = []; + foreach ($parameters as $i => $parameter) { + $parameterPositions[$parameter->getName()] = $i; + } + + /** @var array $argIndexByPosition */ + $argIndexByPosition = []; + $lastPosition = 0; + foreach ($funcCall->getArgs() as $i => $arg) { + if ($arg->unpack) { + return false; + } + if ($arg->name === null) { + $argIndexByPosition[$i] = $i; + $lastPosition = max($lastPosition, $i); + continue; + } + + $position = $parameterPositions[$arg->name->toString()] ?? null; + if ( + $position === null + // a named argument that is collected by the variadic parameter keeps its string key + || $parameters[$position]->isVariadic() + || array_key_exists($position, $argIndexByPosition) + ) { + return false; + } + $argIndexByPosition[$position] = $i; + $lastPosition = max($lastPosition, $position); + } + + /** @var array $defaultValues */ + $defaultValues = []; + for ($position = 0; $position < $lastPosition; $position++) { + if (array_key_exists($position, $argIndexByPosition)) { + continue; + } + + $parameter = $parameters[$position]; + $defaultValue = $parameter->getDefaultValue(); + if (!$parameter->isOptional() || !$parameter->passedByReference()->no() || $defaultValue === null) { + return false; + } + + if ($defaultValue->isConstantArray()->yes() && $defaultValue->isIterableAtLeastOnce()->no()) { + $defaultValues[$position] = new Expr\Array_([]); + continue; + } + + $scalarValues = $defaultValue->getConstantScalarValues(); + if (count($scalarValues) !== 1) { + return false; + } + $scalarValue = $scalarValues[0]; + $defaultValues[$position] = match (true) { + $scalarValue === null => new Expr\ConstFetch(new Node\Name('null')), + is_bool($scalarValue) => new Expr\ConstFetch(new Node\Name($scalarValue ? 'true' : 'false')), + is_int($scalarValue) => new Node\Scalar\Int_($scalarValue), + is_float($scalarValue) => new Node\Scalar\Float_($scalarValue), + default => new Node\Scalar\String_($scalarValue), + }; + } + + return static function (Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $call) use ($argIndexByPosition, $defaultValues, $lastPosition): Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ { + $callArgs = $call->getArgs(); + $args = []; + for ($position = 0; $position <= $lastPosition; $position++) { + if (!array_key_exists($position, $argIndexByPosition)) { + $args[] = new Node\Arg(clone $defaultValues[$position]); + continue; + } + + $arg = clone $callArgs[$argIndexByPosition[$position]]; + $arg->name = null; + $args[] = $arg; + } + $call->args = $args; + + return $call; + }; + } + private function callReturnsByReference(Expr $expr, Scope $scope): bool { if ($expr instanceof Node\Expr\MethodCall) { diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 5c91f0b5c6c..a65228446cd 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2244,6 +2244,12 @@ public function testNoNamedArguments(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testFixNoNamedArguments(): void + { + $this->fix(__DIR__ . '/data/fix-no-named-arguments.php', __DIR__ . '/data/fix-no-named-arguments.php.fixed'); + } + public function testBug11056(): void { $this->checkExplicitMixed = true; diff --git a/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php new file mode 100644 index 00000000000..984c6ee4678 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php @@ -0,0 +1,52 @@ += 8.0 + +namespace FixNoNamedArgumentsFunction; + +/** + * @no-named-arguments + */ +function foo(int $a, int $b = 2, ?string $c = null, bool $d = false, array $e = [], float $f = -1.5, string $g = 'x'): void +{ + +} + +/** + * @no-named-arguments + */ +function byRef(int $a, &$b = null, int $c = 3): void +{ + +} + +/** + * @no-named-arguments + */ +function variadic(int $a, int ...$rest): void +{ + +} + +/** + * @no-named-arguments + */ +function objectDefault(int $a, \stdClass $b = new \stdClass(), int $c = 3): void +{ + +} + +function (): void { + foo(a: 1); + foo(b: 5, a: 1); + foo(1, c: 'c'); + foo( + 1, + g: 'y', + d: true, + ); + foo(a: foo(b: 3, a: 4), e: [1]); + byRef(1, c: 4); + variadic(a: 1); + variadic(1, 2, x: 3); + objectDefault(1, c: 4); + objectDefault(1, b: new \stdClass()); +}; diff --git a/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed new file mode 100644 index 00000000000..211dbf2537b --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed @@ -0,0 +1,56 @@ += 8.0 + +namespace FixNoNamedArgumentsFunction; + +/** + * @no-named-arguments + */ +function foo(int $a, int $b = 2, ?string $c = null, bool $d = false, array $e = [], float $f = -1.5, string $g = 'x'): void +{ + +} + +/** + * @no-named-arguments + */ +function byRef(int $a, &$b = null, int $c = 3): void +{ + +} + +/** + * @no-named-arguments + */ +function variadic(int $a, int ...$rest): void +{ + +} + +/** + * @no-named-arguments + */ +function objectDefault(int $a, \stdClass $b = new \stdClass(), int $c = 3): void +{ + +} + +function (): void { + foo(1); + foo(1, 5); + foo(1, 2, 'c'); + foo( + 1, + 2, + null, + true, + [], + -1.5, + 'y', + ); + foo(foo(4, 3), 2, null, false, [1]); + byRef(1, c: 4); + variadic(1); + variadic(1, 2, x: 3); + objectDefault(1, c: 4); + objectDefault(1, new \stdClass()); +}; From 41241b5fbd08e7fff0a523e506a809f7789cc556 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Mon, 2 Nov 2026 12:00:27 +0000 Subject: [PATCH 2/3] Reorder arguments of the positional arguments fix with ArgumentsNormalizer Also replaces the match expression the PHP 7.4 downgrade does not handle. Amp-Thread-ID: https://ampcode.com/threads/T-01a19c14-ede5-7153-b7e4-e784ff94be68 --- src/Rules/FunctionCallParametersCheck.php | 121 ++++++++++-------- .../Functions/data/fix-no-named-arguments.php | 2 + .../data/fix-no-named-arguments.php.fixed | 2 + 3 files changed, 74 insertions(+), 51 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 0d7d444e89b..43a9c6594f5 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -5,6 +5,7 @@ use Closure; use PhpParser\Node; use PhpParser\Node\Expr; +use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\CollectedDataEmitter; use PHPStan\Analyser\DependencyTracker; use PHPStan\Analyser\MutatingScope; @@ -12,6 +13,7 @@ use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\FunctionReflection; @@ -916,80 +918,69 @@ private function createPositionalArgumentsFix( return false; } - $parameters = $parametersAcceptor->getParameters(); - $parameterPositions = []; - foreach ($parameters as $i => $parameter) { - $parameterPositions[$parameter->getName()] = $i; - } - - /** @var array $argIndexByPosition */ - $argIndexByPosition = []; - $lastPosition = 0; - foreach ($funcCall->getArgs() as $i => $arg) { + $args = $funcCall->getArgs(); + foreach ($args as $arg) { if ($arg->unpack) { return false; } - if ($arg->name === null) { - $argIndexByPosition[$i] = $i; - $lastPosition = max($lastPosition, $i); - continue; - } + } - $position = $parameterPositions[$arg->name->toString()] ?? null; - if ( - $position === null - // a named argument that is collected by the variadic parameter keeps its string key - || $parameters[$position]->isVariadic() - || array_key_exists($position, $argIndexByPosition) - ) { - return false; - } - $argIndexByPosition[$position] = $i; - $lastPosition = max($lastPosition, $position); + $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptor, $args); + if ($reorderedArgs === null) { + return false; } + $parameters = $parametersAcceptor->getParameters(); + $passedArgsCount = 0; /** @var array $defaultValues */ $defaultValues = []; - for ($position = 0; $position < $lastPosition; $position++) { - if (array_key_exists($position, $argIndexByPosition)) { + foreach ($reorderedArgs as $position => $reorderedArg) { + $parameter = $parameters[$position] ?? null; + if ($reorderedArg->value instanceof TypeExpr) { + // reorderArgs() fills skipped optional parameters with their default value types + $defaultValue = $this->createDefaultValueExpr($reorderedArg->value->getExprType()); + if ($parameter === null || !$parameter->passedByReference()->no() || $defaultValue === null) { + return false; + } + $defaultValues[$position] = $defaultValue; continue; } - $parameter = $parameters[$position]; - $defaultValue = $parameter->getDefaultValue(); - if (!$parameter->isOptional() || !$parameter->passedByReference()->no() || $defaultValue === null) { - return false; - } - - if ($defaultValue->isConstantArray()->yes() && $defaultValue->isIterableAtLeastOnce()->no()) { - $defaultValues[$position] = new Expr\Array_([]); + $passedArgsCount++; + /** @var Node\Arg $originalArg */ + $originalArg = $reorderedArg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE); + if ($originalArg->name === null) { continue; } - $scalarValues = $defaultValue->getConstantScalarValues(); - if (count($scalarValues) !== 1) { + // reorderArgs() appends unknown named arguments and puts the ones collected by the variadic parameter at its position, + // passing them positionally would change their meaning + if ($parameter === null || $parameter->isVariadic() || $parameter->getName() !== $originalArg->name->toString()) { return false; } - $scalarValue = $scalarValues[0]; - $defaultValues[$position] = match (true) { - $scalarValue === null => new Expr\ConstFetch(new Node\Name('null')), - is_bool($scalarValue) => new Expr\ConstFetch(new Node\Name($scalarValue ? 'true' : 'false')), - is_int($scalarValue) => new Node\Scalar\Int_($scalarValue), - is_float($scalarValue) => new Node\Scalar\Float_($scalarValue), - default => new Node\Scalar\String_($scalarValue), - }; } - return static function (Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $call) use ($argIndexByPosition, $defaultValues, $lastPosition): Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ { - $callArgs = $call->getArgs(); + // reorderArgs() drops duplicate named arguments + if ($passedArgsCount !== count($args)) { + return false; + } + + return static function (Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $call) use ($parametersAcceptor, $defaultValues): Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ { + $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptor, $call->getArgs()); + if ($reorderedArgs === null) { + throw new ShouldNotHappenException(); + } + $args = []; - for ($position = 0; $position <= $lastPosition; $position++) { - if (!array_key_exists($position, $argIndexByPosition)) { + foreach ($reorderedArgs as $position => $reorderedArg) { + if (array_key_exists($position, $defaultValues)) { $args[] = new Node\Arg(clone $defaultValues[$position]); continue; } - $arg = clone $callArgs[$argIndexByPosition[$position]]; + /** @var Node\Arg $originalArg */ + $originalArg = $reorderedArg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE); + $arg = clone $originalArg; $arg->name = null; $args[] = $arg; } @@ -999,6 +990,34 @@ private function createPositionalArgumentsFix( }; } + private function createDefaultValueExpr(Type $type): ?Expr + { + if ($type->isConstantArray()->yes() && $type->isIterableAtLeastOnce()->no()) { + return new Expr\Array_([]); + } + + $scalarValues = $type->getConstantScalarValues(); + if (count($scalarValues) !== 1) { + return null; + } + + $scalarValue = $scalarValues[0]; + if ($scalarValue === null) { + return new Expr\ConstFetch(new Node\Name('null')); + } + if (is_bool($scalarValue)) { + return new Expr\ConstFetch(new Node\Name($scalarValue ? 'true' : 'false')); + } + if (is_int($scalarValue)) { + return new Node\Scalar\Int_($scalarValue); + } + if (is_float($scalarValue)) { + return new Node\Scalar\Float_($scalarValue); + } + + return new Node\Scalar\String_($scalarValue); + } + private function callReturnsByReference(Expr $expr, Scope $scope): bool { if ($expr instanceof Node\Expr\MethodCall) { diff --git a/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php index 984c6ee4678..e68c823b586 100644 --- a/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php +++ b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php @@ -49,4 +49,6 @@ function (): void { variadic(1, 2, x: 3); objectDefault(1, c: 4); objectDefault(1, b: new \stdClass()); + foo(1, unknown: 2); + foo(b: 1, b: 2, a: 3); }; diff --git a/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed index 211dbf2537b..927744524cc 100644 --- a/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed +++ b/tests/PHPStan/Rules/Functions/data/fix-no-named-arguments.php.fixed @@ -53,4 +53,6 @@ function (): void { variadic(1, 2, x: 3); objectDefault(1, c: 4); objectDefault(1, new \stdClass()); + foo(1, unknown: 2); + foo(b: 1, b: 2, a: 3); }; From 8bc4bfc535c12aff7a4de507cb4c2a7c73776933 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Mon, 2 Nov 2026 12:02:42 +0000 Subject: [PATCH 3/3] Map named arguments to positions without ArgumentsNormalizer reorderArgs() is lenient for analysis purposes (appends unknown names, drops duplicates, moves named variadic arguments), so the fix maps arguments to parameter positions itself again. Keeps the if-chain that replaced the match expression the PHP 7.4 downgrade does not handle. Amp-Thread-ID: https://ampcode.com/threads/T-01a19c14-ede5-7153-b7e4-e784ff94be68 --- src/Rules/FunctionCallParametersCheck.php | 83 +++++++++++------------ 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 43a9c6594f5..f57854da642 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -5,7 +5,6 @@ use Closure; use PhpParser\Node; use PhpParser\Node\Expr; -use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\CollectedDataEmitter; use PHPStan\Analyser\DependencyTracker; use PHPStan\Analyser\MutatingScope; @@ -13,7 +12,6 @@ use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; -use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\FunctionReflection; @@ -918,69 +916,68 @@ private function createPositionalArgumentsFix( return false; } - $args = $funcCall->getArgs(); - foreach ($args as $arg) { + $parameters = $parametersAcceptor->getParameters(); + $parameterPositions = []; + foreach ($parameters as $i => $parameter) { + $parameterPositions[$parameter->getName()] = $i; + } + + /** @var array $argIndexByPosition */ + $argIndexByPosition = []; + $lastPosition = 0; + foreach ($funcCall->getArgs() as $i => $arg) { if ($arg->unpack) { return false; } - } + if ($arg->name === null) { + $argIndexByPosition[$i] = $i; + $lastPosition = max($lastPosition, $i); + continue; + } - $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptor, $args); - if ($reorderedArgs === null) { - return false; + $position = $parameterPositions[$arg->name->toString()] ?? null; + if ( + $position === null + // a named argument that is collected by the variadic parameter keeps its string key + || $parameters[$position]->isVariadic() + || array_key_exists($position, $argIndexByPosition) + ) { + return false; + } + $argIndexByPosition[$position] = $i; + $lastPosition = max($lastPosition, $position); } - $parameters = $parametersAcceptor->getParameters(); - $passedArgsCount = 0; /** @var array $defaultValues */ $defaultValues = []; - foreach ($reorderedArgs as $position => $reorderedArg) { - $parameter = $parameters[$position] ?? null; - if ($reorderedArg->value instanceof TypeExpr) { - // reorderArgs() fills skipped optional parameters with their default value types - $defaultValue = $this->createDefaultValueExpr($reorderedArg->value->getExprType()); - if ($parameter === null || !$parameter->passedByReference()->no() || $defaultValue === null) { - return false; - } - $defaultValues[$position] = $defaultValue; + for ($position = 0; $position < $lastPosition; $position++) { + if (array_key_exists($position, $argIndexByPosition)) { continue; } - $passedArgsCount++; - /** @var Node\Arg $originalArg */ - $originalArg = $reorderedArg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE); - if ($originalArg->name === null) { - continue; + $parameter = $parameters[$position]; + $defaultValue = $parameter->getDefaultValue(); + if (!$parameter->isOptional() || !$parameter->passedByReference()->no() || $defaultValue === null) { + return false; } - // reorderArgs() appends unknown named arguments and puts the ones collected by the variadic parameter at its position, - // passing them positionally would change their meaning - if ($parameter === null || $parameter->isVariadic() || $parameter->getName() !== $originalArg->name->toString()) { + $defaultValueExpr = $this->createDefaultValueExpr($defaultValue); + if ($defaultValueExpr === null) { return false; } + $defaultValues[$position] = $defaultValueExpr; } - // reorderArgs() drops duplicate named arguments - if ($passedArgsCount !== count($args)) { - return false; - } - - return static function (Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $call) use ($parametersAcceptor, $defaultValues): Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ { - $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptor, $call->getArgs()); - if ($reorderedArgs === null) { - throw new ShouldNotHappenException(); - } - + return static function (Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $call) use ($argIndexByPosition, $defaultValues, $lastPosition): Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ { + $callArgs = $call->getArgs(); $args = []; - foreach ($reorderedArgs as $position => $reorderedArg) { - if (array_key_exists($position, $defaultValues)) { + for ($position = 0; $position <= $lastPosition; $position++) { + if (!array_key_exists($position, $argIndexByPosition)) { $args[] = new Node\Arg(clone $defaultValues[$position]); continue; } - /** @var Node\Arg $originalArg */ - $originalArg = $reorderedArg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE); - $arg = clone $originalArg; + $arg = clone $callArgs[$argIndexByPosition[$position]]; $arg->name = null; $args[] = $arg; }