Skip to content

Commit 745b400

Browse files
phpstan-botclaudestaabm
authored
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 8e0e3af commit 745b400

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
@@ -4078,4 +4078,18 @@ public function testBug8048(): void
40784078
$this->analyse([__DIR__ . '/data/bug-8048.php'], []);
40794079
}
40804080

4081+
#[RequiresPhp('>= 8.0.0')]
4082+
public function testBug14596(): void
4083+
{
4084+
$this->checkThisOnly = false;
4085+
$this->checkNullables = true;
4086+
$this->checkUnionTypes = true;
4087+
$this->analyse([__DIR__ . '/data/bug-14596.php'], [
4088+
[
4089+
'Named argument cannot be followed by a positional argument.',
4090+
11,
4091+
],
4092+
]);
4093+
}
4094+
40814095
}

tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php

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

1024+
#[RequiresPhp('>= 8.0.0')]
1025+
public function testBug14596(): void
1026+
{
1027+
$this->checkThisOnly = false;
1028+
$this->analyse([__DIR__ . '/data/bug-14596.php'], [
1029+
[
1030+
'Named argument cannot be followed by a positional argument.',
1031+
12,
1032+
],
1033+
]);
1034+
}
1035+
10241036
}
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)