Skip to content

Commit a59c15c

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix phpstan/phpstan#11978: Intersection type method calls should not depend on ordering
- IntersectionTypeMethodReflection::getVariants() now picks the method with the most parameters instead of always using the first method - Added regression test in tests/PHPStan/Rules/Methods/data/bug-11978.php - The root cause was that the first method's parameter list was always used, so ViewA&ViewB would use ViewA's 0-param render() while ViewB&ViewA would use ViewB's 1-param render()
1 parent 06ea1e1 commit a59c15c

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed

src/Reflection/Type/IntersectionTypeMethodReflection.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ public function getVariants(): array
9595
$returnType = TypeCombinator::intersect(...$returnTypes);
9696
$phpDocReturnType = TypeCombinator::intersect(...$phpDocReturnTypes);
9797
$nativeReturnType = TypeCombinator::intersect(...$nativeReturnTypes);
98+
$methodWithMostParameters = $this->methods[0];
99+
$maxParameters = 0;
100+
foreach ($this->methods as $method) {
101+
foreach ($method->getVariants() as $variant) {
102+
if (count($variant->getParameters()) <= $maxParameters) {
103+
continue;
104+
}
105+
106+
$maxParameters = count($variant->getParameters());
107+
$methodWithMostParameters = $method;
108+
}
109+
}
110+
98111
return array_map(static fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant(
99112
$acceptor->getTemplateTypeMap(),
100113
$acceptor->getResolvedTemplateTypeMap(),
@@ -104,7 +117,7 @@ public function getVariants(): array
104117
$phpDocReturnType,
105118
$nativeReturnType,
106119
$acceptor->getCallSiteVarianceMap(),
107-
), $this->methods[0]->getVariants());
120+
), $methodWithMostParameters->getVariants());
108121
}
109122

110123
public function getOnlyVariant(): ExtendedParametersAcceptor

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3962,4 +3962,12 @@ public function testBug11463(): void
39623962
]);
39633963
}
39643964

3965+
public function testBug11978(): void
3966+
{
3967+
$this->checkThisOnly = false;
3968+
$this->checkNullables = true;
3969+
$this->checkUnionTypes = true;
3970+
$this->analyse([__DIR__ . '/data/bug-11978.php'], []);
3971+
}
3972+
39653973
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug11978;
6+
7+
interface ViewA {
8+
public function render(): string;
9+
}
10+
interface ViewB {
11+
public function render(string $foo = ''): string;
12+
}
13+
14+
class Foo
15+
{
16+
public function __construct(
17+
private readonly ViewA&ViewB $view1,
18+
private readonly ViewB&ViewA $view2,
19+
) {}
20+
21+
public function renderFoo(string $foo): string
22+
{
23+
$a = $this->view1->render($foo);
24+
$b = $this->view2->render($foo);
25+
26+
return $a . $b;
27+
}
28+
}

0 commit comments

Comments
 (0)