Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Php/PhpVersions.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public function supportsNamedArguments(): TrinaryLogic
return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result;
}

public function supportsNamedArgumentAfterUnpackedArgument(): TrinaryLogic
{
return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result;
}

}
23 changes: 13 additions & 10 deletions src/Rules/FunctionCallParametersCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public function check(
$hasUnpackedArgument = false;
$errors = [];
foreach ($args as $arg) {
$argumentName = null;
if ($arg->name !== null) {
$hasNamedArguments = true;
$argumentName = $arg->name->toString();
}

if ($hasNamedArguments && $arg->unpack) {
$errors[] = RuleErrorBuilder::message('Named argument cannot be followed by an unpacked (...) argument.')
->identifier('argument.unpackAfterNamed')
Expand All @@ -109,20 +115,17 @@ public function check(
->build();
}
if ($hasUnpackedArgument && !$arg->unpack) {
$errors[] = RuleErrorBuilder::message('Unpacked argument (...) cannot be followed by a non-unpacked argument.')
->identifier('argument.nonUnpackAfterUnpacked')
->line($arg->getStartLine())
->nonIgnorable()
->build();
if ($argumentName === null || !$scope->getPhpVersion()->supportsNamedArgumentAfterUnpackedArgument()->yes()) {
$errors[] = RuleErrorBuilder::message('Unpacked argument (...) cannot be followed by a non-unpacked argument.')
->identifier('argument.nonUnpackAfterUnpacked')
->line($arg->getStartLine())
->nonIgnorable()
->build();
}
}
if ($arg->unpack) {
$hasUnpackedArgument = true;
}
$argumentName = null;
if ($arg->name !== null) {
$hasNamedArguments = true;
$argumentName = $arg->name->toString();
}
if ($arg->unpack) {
$type = $scope->getType($arg->value);
$arrays = $type->getConstantArrays();
Expand Down
32 changes: 32 additions & 0 deletions tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,20 @@ public function testNamedArguments(): void
$this->analyse([__DIR__ . '/data/named-arguments.php'], $errors);
}

public function testNamedArgumentsAfterUnpacking(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/data/named-arguments-after-unpacking.php'], [
[
'Named parameter cannot overwrite already unpacked argument $b.',
14,
],
]);
}

public function testBug4514(): void
{
$this->analyse([__DIR__ . '/data/bug-4514.php'], []);
Expand Down Expand Up @@ -1936,4 +1950,22 @@ public function testBug12051(): void
$this->analyse([__DIR__ . '/data/bug-12051.php'], []);
}

public function testBug8046(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/data/bug-8046.php'], []);
}

public function testBug11418(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/data/bug-11418.php'], []);
}

}
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-11418.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Bug11418;

function foo(int $a, int $b, int $c = 3, int $d = 4): int {
return $a + $b + $c + $d;
}

var_dump(foo(...[1, 2], d: 40)); // 46
11 changes: 11 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-8046.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);

namespace Bug8046;

function add(int $a, int $b): int {
return $a + $b;
}

$args = ['a' => 7];

var_dump(add(...$args, b: 8));
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace FunctionNamedArgumentsAfterUnpacking;

// https://www.php.net/manual/en/functions.arguments.php#example-180

function foo($a, $b, $c = 3, $d = 4) {
return $a + $b + $c + $d;
}

var_dump(foo(...[1, 2], d: 40)); // 46
var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46

var_dump(foo(...[1, 2], b: 20)); // Fatal error. Named parameter $b overwrites previous argument
Loading