Skip to content

Commit 57dde1b

Browse files
committed
Fix intersection type losing __invoke() return type
- IntersectionType::getCallableParametersAcceptors() returned TrivialParametersAcceptor, losing all type info - Now delegates to inner types, preferring types that are definitely callable over maybe-callable - New regression test in tests/PHPStan/Analyser/nsrt/bug-14362.php
1 parent b6c48a0 commit 57dde1b

2 files changed

Lines changed: 66 additions & 4 deletions

File tree

src/Type/IntersectionType.php

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use PHPStan\Reflection\MissingConstantFromReflectionException;
1717
use PHPStan\Reflection\MissingMethodFromReflectionException;
1818
use PHPStan\Reflection\MissingPropertyFromReflectionException;
19-
use PHPStan\Reflection\TrivialParametersAcceptor;
2019
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedMethodPrototypeReflection;
2120
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedPropertyPrototypeReflection;
2221
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
@@ -46,6 +45,7 @@
4645
use function array_filter;
4746
use function array_intersect_key;
4847
use function array_map;
48+
use function array_merge;
4949
use function array_shift;
5050
use function array_unique;
5151
use function array_values;
@@ -1124,11 +1124,31 @@ public function isCallable(): TrinaryLogic
11241124

11251125
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
11261126
{
1127-
if ($this->isCallable()->no()) {
1128-
throw new ShouldNotHappenException();
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 = array_merge($yesAcceptors, $type->getCallableParametersAcceptors($scope));
1138+
} else {
1139+
$maybeAcceptors = array_merge($maybeAcceptors, $type->getCallableParametersAcceptors($scope));
1140+
}
1141+
}
1142+
1143+
if (count($yesAcceptors) > 0) {
1144+
return $yesAcceptors;
1145+
}
1146+
1147+
if (count($maybeAcceptors) > 0) {
1148+
return $maybeAcceptors;
11291149
}
11301150

1131-
return [new TrivialParametersAcceptor()];
1151+
throw new ShouldNotHappenException();
11321152
}
11331153

11341154
public function isCloneable(): TrinaryLogic
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14362;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
interface A
10+
{
11+
public function __invoke(B $b): int;
12+
}
13+
14+
interface B
15+
{
16+
17+
}
18+
19+
class C {
20+
public static function u(): A&B {
21+
return new class() implements A, B {
22+
public function __invoke(B $b): int {
23+
return 1;
24+
}
25+
};
26+
}
27+
}
28+
29+
class D {
30+
public static function u(): A {
31+
return new class() implements A {
32+
public function __invoke(B $b): int {
33+
return 1;
34+
}
35+
};
36+
}
37+
}
38+
39+
function () : void {
40+
assertType('Closure(Bug14362\B): int', C::u()(...));
41+
assertType('Closure(Bug14362\B): int', D::u()(...));
42+
};

0 commit comments

Comments
 (0)