From 0d5c984885350c28ca829ee1a37f5b1c983a7123 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Tue, 15 Feb 2022 00:22:46 +0100 Subject: [PATCH] Detect closure parameter types when passing closure in a union --- src/Analyser/NodeScopeResolver.php | 15 +++++++++++++++ .../PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++++ .../PHPStan/Analyser/data/callable-in-union.php | 17 +++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/callable-in-union.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e18aa40ba2..65e63df952 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -139,6 +139,7 @@ use PHPStan\Type\VoidType; use Throwable; use function array_fill_keys; +use function array_filter; use function array_key_exists; use function array_map; use function array_merge; @@ -2858,6 +2859,13 @@ private function processClosureNode( $byRefUses = []; if ($passedToType !== null && !$passedToType->isCallable()->no()) { + if ($passedToType instanceof UnionType) { + $passedToType = TypeCombinator::union(...array_filter( + $passedToType->getTypes(), + static fn (Type $type) => $type->isCallable()->yes(), + )); + } + $callableParameters = null; $acceptors = $passedToType->getCallableParametersAcceptors($scope); if (count($acceptors) === 1) { @@ -2987,6 +2995,13 @@ private function processArrowFunctionNode( } if ($passedToType !== null && !$passedToType->isCallable()->no()) { + if ($passedToType instanceof UnionType) { + $passedToType = TypeCombinator::union(...array_filter( + $passedToType->getTypes(), + static fn (Type $type) => $type->isCallable()->yes(), + )); + } + $callableParameters = null; $acceptors = $passedToType->getCallableParametersAcceptors($scope); if (count($acceptors) === 1) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 57256e2511..05311f1aa2 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -714,6 +714,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6672.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6687.php'); + if (self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/callable-in-union.php'); + } + require_once __DIR__ . '/data/countable.php'; yield from $this->gatherAssertTypes(__DIR__ . '/data/countable.php'); } diff --git a/tests/PHPStan/Analyser/data/callable-in-union.php b/tests/PHPStan/Analyser/data/callable-in-union.php new file mode 100644 index 0000000000..b72c0725cc --- /dev/null +++ b/tests/PHPStan/Analyser/data/callable-in-union.php @@ -0,0 +1,17 @@ +|(callable(array): array) $_ */ +function acceptArrayOrCallable($_) +{ +} + +acceptArrayOrCallable(fn ($parameter) => assertType('array', $parameter)); + +acceptArrayOrCallable(function ($parameter) { + assertType('array', $parameter); + return $parameter; +});