Skip to content

Commit aebc2bb

Browse files
phpstan-botclaude
andcommitted
Use intersection method resolution for getCallableParametersAcceptors()
Instead of collecting and merging callable acceptors from individual types (which is union-like behavior), leverage IntersectionType's own __invoke method resolution via IntersectionTypeMethodReflection. This properly intersects return types when multiple types define __invoke. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a27e585 commit aebc2bb

2 files changed

Lines changed: 27 additions & 19 deletions

File tree

src/Type/IntersectionType.php

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
use PHPStan\Reflection\ExtendedPropertyReflection;
1515
use PHPStan\Reflection\InitializerExprTypeResolver;
1616
use PHPStan\Reflection\MissingConstantFromReflectionException;
17+
use PHPStan\Reflection\Callables\FunctionCallableVariant;
1718
use PHPStan\Reflection\MissingMethodFromReflectionException;
1819
use PHPStan\Reflection\MissingPropertyFromReflectionException;
20+
use PHPStan\Reflection\TrivialParametersAcceptor;
1921
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedMethodPrototypeReflection;
2022
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedPropertyPrototypeReflection;
2123
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
@@ -45,7 +47,6 @@
4547
use function array_filter;
4648
use function array_intersect_key;
4749
use function array_map;
48-
use function array_merge;
4950
use function array_shift;
5051
use function array_unique;
5152
use function array_values;
@@ -1124,28 +1125,21 @@ public function isCallable(): TrinaryLogic
11241125

11251126
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
11261127
{
1127-
$yesAcceptors = [];
1128-
$maybeAcceptors = [];
1129-
1130-
foreach ($this->types as $type) {
1131-
$isCallable = $type->isCallable();
1132-
if ($isCallable->no()) {
1133-
continue;
1134-
}
1135-
1136-
if ($isCallable->yes()) {
1137-
$yesAcceptors[] = $type->getCallableParametersAcceptors($scope);
1138-
} else {
1139-
$maybeAcceptors[] = $type->getCallableParametersAcceptors($scope);
1140-
}
1128+
if ($this->hasMethod('__invoke')->yes()) {
1129+
$method = $this->getMethod('__invoke', $scope);
1130+
return FunctionCallableVariant::createFromVariants($method, $method->getVariants());
11411131
}
11421132

1143-
if (count($yesAcceptors) > 0) {
1144-
return array_merge(...$yesAcceptors);
1133+
if ($this->isCallable()->yes()) {
1134+
foreach ($this->types as $type) {
1135+
if ($type->isCallable()->yes()) {
1136+
return $type->getCallableParametersAcceptors($scope);
1137+
}
1138+
}
11451139
}
11461140

1147-
if (count($maybeAcceptors) > 0) {
1148-
return array_merge(...$maybeAcceptors);
1141+
if ($this->isCallable()->maybe()) {
1142+
return [new TrivialParametersAcceptor()];
11491143
}
11501144

11511145
throw new ShouldNotHappenException();

tests/PHPStan/Analyser/nsrt/bug-14362.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ interface E
4141

4242
}
4343

44+
interface F
45+
{
46+
public function __invoke(B $b): string;
47+
}
48+
4449
class G {
4550
public static function u(): A&B&E {
4651
return new class() implements A, B, E {
@@ -58,6 +63,12 @@ public static function u(): B&E {
5863
}
5964
}
6065

66+
class I {
67+
public static function u(): A&F {
68+
throw new \Exception();
69+
}
70+
}
71+
6172
function doBar() : void {
6273
assertType('Closure(Bug14362\B): int', C::u()(...));
6374
assertType('Closure(Bug14362\B): int', D::u()(...));
@@ -67,6 +78,9 @@ function doBar() : void {
6778

6879
// Intersection with only maybe-callable types (neither has __invoke)
6980
assertType('Closure', H::u()(...));
81+
82+
// Intersection with two yes-callable types (both have __invoke) - return types are intersected
83+
assertType('Closure(Bug14362\B): never', I::u()(...));
7084
}
7185

7286
function doFoo(string $c):void {

0 commit comments

Comments
 (0)