Skip to content

Commit 8e2e04e

Browse files
committed
Handle instance method callables in initializer type resolution
1 parent 9bd33f6 commit 8e2e04e

5 files changed

Lines changed: 113 additions & 0 deletions

File tree

‎src/Reflection/InitializerExprTypeResolver.php‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,10 @@ public function getFirstClassCallableType(Expr\CallLike $expr, InitializerExprCo
938938
);
939939
}
940940

941+
if ($expr instanceof Expr\MethodCall) {
942+
return new ObjectType(Closure::class);
943+
}
944+
941945
if ($expr instanceof New_) {
942946
return new ErrorType();
943947
}

‎tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,21 @@ public function testFirstClassCallables(): void
12301230
$this->analyse([__DIR__ . '/data/first-class-callables.php'], []);
12311231
}
12321232

1233+
public function testBug15432(): void
1234+
{
1235+
$this->analyse([__DIR__ . '/data/bug-15432.php'], []);
1236+
}
1237+
1238+
public function testBug15432Invalid(): void
1239+
{
1240+
$this->analyse([__DIR__ . '/data/bug-15432-invalid.php'], [
1241+
[
1242+
'Parameter #1 $callbacks of function Bug15432Invalid\\consume expects array<\'closure\'|\'method\', callable(int): string>, array{method: Closure(string): int, closure: Closure(int): decimal-int-string} given.',
1243+
23,
1244+
],
1245+
]);
1246+
}
1247+
12331248
public function testBug4413(): void
12341249
{
12351250
require_once __DIR__ . '/data/bug-4413.php';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug15432Invalid;
4+
5+
/**
6+
* @template T of string
7+
* @param array<T, callable(int): string> $callbacks
8+
*/
9+
function consume(array $callbacks): void
10+
{
11+
}
12+
13+
class Handler
14+
{
15+
public function handle(string $value): int
16+
{
17+
return strlen($value);
18+
}
19+
}
20+
21+
function invalid(Handler $handler): void
22+
{
23+
consume([
24+
'method' => $handler->handle(...),
25+
'closure' => static fn (int $value): string => (string) $value,
26+
]);
27+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug15432;
4+
5+
/**
6+
* @template T of string
7+
* @param array<T, callable(): void> $callbacks
8+
*/
9+
function consume(array $callbacks): void
10+
{
11+
foreach ($callbacks as $handler) {
12+
$handler();
13+
}
14+
}
15+
16+
consume([
17+
'method' => (new class {
18+
public function handle(): void {}
19+
})->handle(...),
20+
'closure' => function (): void {},
21+
]);
22+
23+
class Handler
24+
{
25+
public function handle(): void {}
26+
}
27+
28+
function instanceMethods(Handler $handler): void
29+
{
30+
consume([
31+
'method' => $handler->handle(...),
32+
'closure' => static function (): void {},
33+
]);
34+
consume([
35+
'method' => $handler->handle(...),
36+
'closure' => fn () => null,
37+
]);
38+
$callable = $handler->handle(...);
39+
consume([
40+
'method' => $callable,
41+
'closure' => static function (): void {},
42+
]);
43+
}
44+
45+
/**
46+
* @template T of string
47+
* @param array<T, array<string, callable(): void>> $callbacks
48+
*/
49+
function consumeNested(array $callbacks): void
50+
{
51+
}
52+
53+
function nested(Handler $handler): void
54+
{
55+
consumeNested([
56+
'callbacks' => [
57+
'method' => $handler->handle(...),
58+
'closure' => static function (): void {},
59+
],
60+
]);
61+
}

‎turbo-ext/src/InitializerExprTypeResolver.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4334,6 +4334,12 @@ class InitializerExprTypeResolver
43344334
return createFirstClassCallable(method.raw(), variants.raw(), nativeTypesPromoted);
43354335
}
43364336

4337+
int isMethodCall = isAClass(expr, PT_CLASS_METHOD_CALL);
4338+
if (UNEXPECTED(isMethodCall < 0)) return zv::Val();
4339+
if (isMethodCall) {
4340+
return newObjectTypeLiteral(PT_LC("Closure"));
4341+
}
4342+
43374343
int isNew = isAClass(expr, PT_CLASS_NEW);
43384344
if (UNEXPECTED(isNew < 0)) return zv::Val();
43394345
if (isNew) {

0 commit comments

Comments
 (0)