Skip to content
2 changes: 1 addition & 1 deletion src/Analyser/ArgumentsNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array
continue;
}
if (!array_key_exists($j, $signatureParameters)) {
throw new ShouldNotHappenException('Parameter signatures cannot have holes');
return null;
}

$parameter = $signatureParameters[$j];
Expand Down
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,13 @@ public function testBug14550(): void
$this->assertNotEmpty($errors);
}

public function testBug14596(): void
{
// crash
$errors = $this->runAnalyse(__DIR__ . '/data/bug-14596.php');
$this->assertNotEmpty($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return list<Error>
Expand Down
27 changes: 27 additions & 0 deletions tests/PHPStan/Analyser/ArgumentsNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,33 @@ public static function dataReorderInvalid(): iterable
[new StringType(), 'three'],
],
];

// positional arg after named arg with variadic parameter
yield [
[
['value', false, false, null],
['values', true, true, new StringType()],
],
[
[new IntegerType(), null],
[new IntegerType(), null],
[new IntegerType(), null],
[new StringType(), 'd'],
[new IntegerType(), null],
],
];

// positional arg after named arg without variadic parameter
yield [
[
['one', false, false, null],
],
[
[new IntegerType(), null],
[new StringType(), 'd'],
[new IntegerType(), null],
],
];
}

/**
Expand Down
34 changes: 34 additions & 0 deletions tests/PHPStan/Analyser/data/bug-14596.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Bug14596;

function foo(int $a, int $b, int $c, string ...$rest): void {}

class Foo {
public function bar(int $a, int $b, int $c, string ...$rest): void {}
public static function baz(int $a, int $b, int $c, string ...$rest): void {}
public function __construct(int $a, int $b, int $c, string ...$rest) {}
}

// built-in function
\PHPStan\dumpType(1, 2, 3, d: 'foo', 5);
Comment thread
staabm marked this conversation as resolved.

// user-defined function
foo(1, 2, 3, d: 'foo', 5);

// method call
$obj = new Foo(1, 2, 3);
$obj->bar(1, 2, 3, d: 'foo', 5);

// static method call
Foo::baz(1, 2, 3, d: 'foo', 5);
Comment on lines +19 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phpstan-bot cover this example also in CallMethodsRuleTest, CallStaticMethodsRuleTest and assert expected errors

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed. Added testBug14596() to both CallMethodsRuleTest and CallStaticMethodsRuleTest, with a shared test data file at tests/PHPStan/Rules/Methods/data/bug-14596.php that exercises method calls and static method calls with positional arguments after named arguments. Both tests assert the expected "Named argument cannot be followed by a positional argument." error. All 12048 tests pass and make phpstan reports no errors.


// constructor
new Foo(1, 2, 3, d: 'foo', 5);

// closure
$closure = function (int $a, int $b, int $c, string ...$rest): void {};
$closure(1, 2, 3, d: 'foo', 5);

// call_user_func
call_user_func('Bug14596\foo', 1, 2, 3, d: 'foo', 5);
14 changes: 14 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4078,4 +4078,18 @@ public function testBug8048(): void
$this->analyse([__DIR__ . '/data/bug-8048.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug14596(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/data/bug-14596.php'], [
[
'Named argument cannot be followed by a positional argument.',
11,
],
]);
}

}
12 changes: 12 additions & 0 deletions tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1021,4 +1021,16 @@ public function testBug11894(): void
$this->analyse([__DIR__ . '/data/bug-11894.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testBug14596(): void
{
$this->checkThisOnly = false;
$this->analyse([__DIR__ . '/data/bug-14596.php'], [
[
'Named argument cannot be followed by a positional argument.',
12,
],
]);
}

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

namespace Bug14596Methods;

class Foo {
public function bar(int $a, int $b, int $c, string ...$rest): void {}
public static function baz(int $a, int $b, int $c, string ...$rest): void {}
}

function (Foo $obj): void {
$obj->bar(1, 2, 3, d: 'foo', 5);
Foo::baz(1, 2, 3, d: 'foo', 5);
};
Loading