Skip to content

Commit f091c56

Browse files
committed
Fix variadic parameters for generics
1 parent 39e8286 commit f091c56

File tree

4 files changed

+65
-6
lines changed

4 files changed

+65
-6
lines changed

src/Reflection/GenericParametersAcceptorResolver.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
use PHPStan\Type\ErrorType;
66
use PHPStan\Type\Generic\TemplateTypeMap;
77
use PHPStan\Type\Type;
8+
use PHPStan\Type\TypeCombinator;
9+
use function array_key_exists;
810
use function array_merge;
11+
use function count;
12+
use function is_int;
913

1014
class GenericParametersAcceptorResolver
1115
{
@@ -18,15 +22,38 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc
1822
{
1923
$typeMap = TemplateTypeMap::createEmpty();
2024

21-
foreach ($parametersAcceptor->getParameters() as $i => $param) {
22-
if (isset($argTypes[$i])) {
23-
$argType = $argTypes[$i];
24-
} elseif (isset($argTypes[$param->getName()])) {
25-
$argType = $argTypes[$param->getName()];
25+
$parameters = $parametersAcceptor->getParameters();
26+
$namedArgTypes = [];
27+
foreach ($argTypes as $i => $argType) {
28+
if (is_int($i)) {
29+
if (isset($parameters[$i])) {
30+
$namedArgTypes[$parameters[$i]->getName()] = $argType;
31+
continue;
32+
}
33+
if (count($parameters) > 0) {
34+
$lastParameter = $parameters[count($parameters) - 1];
35+
if ($lastParameter->isVariadic()) {
36+
$parameterName = $lastParameter->getName();
37+
if (array_key_exists($parameterName, $namedArgTypes)) {
38+
$namedArgTypes[$parameterName] = TypeCombinator::union($namedArgTypes[$parameterName], $argType);
39+
continue;
40+
}
41+
$namedArgTypes[$parameterName] = $argType;
42+
}
43+
}
44+
continue;
45+
}
46+
47+
$namedArgTypes[$i] = $argType;
48+
}
49+
50+
foreach ($parametersAcceptor->getParameters() as $param) {
51+
if (isset($namedArgTypes[$param->getName()])) {
52+
$argType = $namedArgTypes[$param->getName()];
2653
} elseif ($param->getDefaultValue() !== null) {
2754
$argType = $param->getDefaultValue();
2855
} else {
29-
break;
56+
continue;
3057
}
3158

3259
$paramType = $param->getType();

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,12 @@ public function testBug6979(): void
647647
$this->assertNoErrors($errors);
648648
}
649649

650+
public function testBug7068(): void
651+
{
652+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7068.php');
653+
$this->assertNoErrors($errors);
654+
}
655+
650656
/**
651657
* @param string[]|null $allAnalysedFiles
652658
* @return Error[]

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@ public function dataFileAsserts(): iterable
867867

868868
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6927.php');
869869
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-optional-set.php');
870+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7068.php');
870871
}
871872

872873
/**
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Bug7068;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @template T
12+
* @param array<T> ...$arrays
13+
* @return array<T>
14+
*/
15+
function merge(array ...$arrays): array {
16+
return array_merge(...$arrays);
17+
}
18+
19+
public function doFoo(): void
20+
{
21+
assertType('array<int>', $this->merge([1, 2], [3, 4], [5]));
22+
assertType('array<int|string>', $this->merge([1, 2], ['foo', 'bar']));
23+
}
24+
25+
}

0 commit comments

Comments
 (0)