Skip to content

Commit c10476d

Browse files
committed
Fix for inferring closure parameter type from callable union
1 parent ffa7686 commit c10476d

File tree

4 files changed

+99
-6
lines changed

4 files changed

+99
-6
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
use PHPStan\Reflection\MethodReflection;
125125
use PHPStan\Reflection\Native\NativeMethodReflection;
126126
use PHPStan\Reflection\Native\NativeParameterReflection;
127+
use PHPStan\Reflection\ParameterReflection;
127128
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
128129
use PHPStan\Reflection\ParametersAcceptor;
129130
use PHPStan\Reflection\ParametersAcceptorSelector;
@@ -3457,8 +3458,39 @@ private function processClosureNode(
34573458
}
34583459

34593460
$acceptors = $passedToType->getCallableParametersAcceptors($scope);
3460-
if (count($acceptors) === 1) {
3461-
$callableParameters = $acceptors[0]->getParameters();
3461+
if (count($acceptors) > 0) {
3462+
foreach ($acceptors as $acceptor) {
3463+
if ($callableParameters === null) {
3464+
$callableParameters = array_map(static fn (ParameterReflection $callableParameter) => new NativeParameterReflection(
3465+
$callableParameter->getName(),
3466+
$callableParameter->isOptional(),
3467+
$callableParameter->getType(),
3468+
$callableParameter->passedByReference(),
3469+
$callableParameter->isVariadic(),
3470+
$callableParameter->getDefaultValue(),
3471+
), $acceptor->getParameters());
3472+
continue;
3473+
}
3474+
3475+
$newParameters = [];
3476+
foreach ($acceptor->getParameters() as $i => $callableParameter) {
3477+
if (!array_key_exists($i, $callableParameters)) {
3478+
$newParameters[] = $callableParameter;
3479+
continue;
3480+
}
3481+
3482+
$newParameters[] = $callableParameters[$i]->union(new NativeParameterReflection(
3483+
$callableParameter->getName(),
3484+
$callableParameter->isOptional(),
3485+
$callableParameter->getType(),
3486+
$callableParameter->passedByReference(),
3487+
$callableParameter->isVariadic(),
3488+
$callableParameter->getDefaultValue(),
3489+
));
3490+
}
3491+
3492+
$callableParameters = $newParameters;
3493+
}
34623494
}
34633495
}
34643496

@@ -3639,8 +3671,39 @@ private function processArrowFunctionNode(
36393671
}
36403672

36413673
$acceptors = $passedToType->getCallableParametersAcceptors($scope);
3642-
if (count($acceptors) === 1) {
3643-
$callableParameters = $acceptors[0]->getParameters();
3674+
if (count($acceptors) > 0) {
3675+
foreach ($acceptors as $acceptor) {
3676+
if ($callableParameters === null) {
3677+
$callableParameters = array_map(static fn (ParameterReflection $callableParameter) => new NativeParameterReflection(
3678+
$callableParameter->getName(),
3679+
$callableParameter->isOptional(),
3680+
$callableParameter->getType(),
3681+
$callableParameter->passedByReference(),
3682+
$callableParameter->isVariadic(),
3683+
$callableParameter->getDefaultValue(),
3684+
), $acceptor->getParameters());
3685+
continue;
3686+
}
3687+
3688+
$newParameters = [];
3689+
foreach ($acceptor->getParameters() as $i => $callableParameter) {
3690+
if (!array_key_exists($i, $callableParameters)) {
3691+
$newParameters[] = $callableParameter;
3692+
continue;
3693+
}
3694+
3695+
$newParameters[] = $callableParameters[$i]->union(new NativeParameterReflection(
3696+
$callableParameter->getName(),
3697+
$callableParameter->isOptional(),
3698+
$callableParameter->getType(),
3699+
$callableParameter->passedByReference(),
3700+
$callableParameter->isVariadic(),
3701+
$callableParameter->getDefaultValue(),
3702+
));
3703+
}
3704+
3705+
$callableParameters = $newParameters;
3706+
}
36443707
}
36453708
}
36463709

src/Reflection/Native/NativeParameterReflection.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\Reflection\ParameterReflection;
66
use PHPStan\Reflection\PassedByReference;
77
use PHPStan\Type\Type;
8+
use PHPStan\Type\TypeCombinator;
89

910
class NativeParameterReflection implements ParameterReflection
1011
{
@@ -50,6 +51,18 @@ public function getDefaultValue(): ?Type
5051
return $this->defaultValue;
5152
}
5253

54+
public function union(self $other): self
55+
{
56+
return new self(
57+
$this->name,
58+
$this->optional && $other->optional,
59+
TypeCombinator::union($this->type, $other->type),
60+
$this->passedByReference->combine($other->passedByReference),
61+
$this->variadic && $other->variadic,
62+
$this->optional && $other->optional ? $this->defaultValue : null,
63+
);
64+
}
65+
5366
/**
5467
* @param mixed[] $properties
5568
*/

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,9 @@ public function dataFileAsserts(): iterable
824824
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6672.php');
825825
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6687.php');
826826

827-
yield from $this->gatherAssertTypes(__DIR__ . '/data/callable-in-union.php');
827+
if (PHP_VERSION_ID >= 70400) {
828+
yield from $this->gatherAssertTypes(__DIR__ . '/data/callable-in-union.php');
829+
}
828830

829831
if (PHP_VERSION_ID < 80000) {
830832
yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_php7.php');

tests/PHPStan/Analyser/data/callable-in-union.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php
1+
<?php // lint >= 7.4
22

33
namespace CallableInUnion;
44

@@ -15,3 +15,18 @@ function acceptArrayOrCallable($_)
1515
assertType('array<string, mixed>', $parameter);
1616
return $parameter;
1717
});
18+
19+
/**
20+
* @param (callable(string): void)|callable(int): void $a
21+
* @return void
22+
*/
23+
function acceptCallableOrCallableLikeArray($a): void
24+
{
25+
26+
}
27+
28+
acceptCallableOrCallableLikeArray(function ($p) {
29+
assertType('int|string', $p);
30+
});
31+
32+
acceptCallableOrCallableLikeArray(fn ($p) => assertType('int|string', $p));

0 commit comments

Comments
 (0)