Skip to content

Commit 460cd50

Browse files
phpstan-botclaudestaabm
committed
Return null from ArgumentsNormalizer::reorderArgs() when positional args after named args create holes beyond parameter count (#5637)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Markus Staab <markus.staab@redaxo.de>
1 parent a9ebd38 commit 460cd50

7 files changed

Lines changed: 108 additions & 1 deletion

File tree

src/Analyser/ArgumentsNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array
403403
continue;
404404
}
405405
if (!array_key_exists($j, $signatureParameters)) {
406-
throw new ShouldNotHappenException('Parameter signatures cannot have holes');
406+
return null;
407407
}
408408

409409
$parameter = $signatureParameters[$j];

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,13 @@ public function testBug14550(): void
15631563
$this->assertNotEmpty($errors);
15641564
}
15651565

1566+
public function testBug14596(): void
1567+
{
1568+
// crash
1569+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-14596.php');
1570+
$this->assertNotEmpty($errors);
1571+
}
1572+
15661573
/**
15671574
* @param string[]|null $allAnalysedFiles
15681575
* @return list<Error>

tests/PHPStan/Analyser/ArgumentsNormalizerTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,33 @@ public static function dataReorderInvalid(): iterable
323323
[new StringType(), 'three'],
324324
],
325325
];
326+
327+
// positional arg after named arg with variadic parameter
328+
yield [
329+
[
330+
['value', false, false, null],
331+
['values', true, true, new StringType()],
332+
],
333+
[
334+
[new IntegerType(), null],
335+
[new IntegerType(), null],
336+
[new IntegerType(), null],
337+
[new StringType(), 'd'],
338+
[new IntegerType(), null],
339+
],
340+
];
341+
342+
// positional arg after named arg without variadic parameter
343+
yield [
344+
[
345+
['one', false, false, null],
346+
],
347+
[
348+
[new IntegerType(), null],
349+
[new StringType(), 'd'],
350+
[new IntegerType(), null],
351+
],
352+
];
326353
}
327354

328355
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Bug14596;
4+
5+
function foo(int $a, int $b, int $c, string ...$rest): void {}
6+
7+
class Foo {
8+
public function bar(int $a, int $b, int $c, string ...$rest): void {}
9+
public static function baz(int $a, int $b, int $c, string ...$rest): void {}
10+
public function __construct(int $a, int $b, int $c, string ...$rest) {}
11+
}
12+
13+
// built-in function
14+
\PHPStan\dumpType(1, 2, 3, d: 'foo', 5);
15+
16+
// user-defined function
17+
foo(1, 2, 3, d: 'foo', 5);
18+
19+
// method call
20+
$obj = new Foo(1, 2, 3);
21+
$obj->bar(1, 2, 3, d: 'foo', 5);
22+
23+
// static method call
24+
Foo::baz(1, 2, 3, d: 'foo', 5);
25+
26+
// constructor
27+
new Foo(1, 2, 3, d: 'foo', 5);
28+
29+
// closure
30+
$closure = function (int $a, int $b, int $c, string ...$rest): void {};
31+
$closure(1, 2, 3, d: 'foo', 5);
32+
33+
// call_user_func
34+
call_user_func('Bug14596\foo', 1, 2, 3, d: 'foo', 5);

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4102,4 +4102,18 @@ public function testBug8048(): void
41024102
$this->analyse([__DIR__ . '/data/bug-8048.php'], []);
41034103
}
41044104

4105+
#[RequiresPhp('>= 8.0.0')]
4106+
public function testBug14596(): void
4107+
{
4108+
$this->checkThisOnly = false;
4109+
$this->checkNullables = true;
4110+
$this->checkUnionTypes = true;
4111+
$this->analyse([__DIR__ . '/data/bug-14596.php'], [
4112+
[
4113+
'Named argument cannot be followed by a positional argument.',
4114+
11,
4115+
],
4116+
]);
4117+
}
4118+
41054119
}

tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,4 +1037,16 @@ public function testBug11894(): void
10371037
$this->analyse([__DIR__ . '/data/bug-11894.php'], []);
10381038
}
10391039

1040+
#[RequiresPhp('>= 8.0.0')]
1041+
public function testBug14596(): void
1042+
{
1043+
$this->checkThisOnly = false;
1044+
$this->analyse([__DIR__ . '/data/bug-14596.php'], [
1045+
[
1046+
'Named argument cannot be followed by a positional argument.',
1047+
12,
1048+
],
1049+
]);
1050+
}
1051+
10401052
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Bug14596Methods;
4+
5+
class Foo {
6+
public function bar(int $a, int $b, int $c, string ...$rest): void {}
7+
public static function baz(int $a, int $b, int $c, string ...$rest): void {}
8+
}
9+
10+
function (Foo $obj): void {
11+
$obj->bar(1, 2, 3, d: 'foo', 5);
12+
Foo::baz(1, 2, 3, d: 'foo', 5);
13+
};

0 commit comments

Comments
 (0)