diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d8ee132a00..7b8c92729f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1308,7 +1308,7 @@ private function resolveType(Expr $node): Type } if ($node instanceof Expr\ArrowFunction) { - $returnType = $this->getType($node->expr); + $returnType = $this->enterArrowFunctionWithoutReflection($node)->getType($node->expr); if ($node->returnType !== null) { $returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType); } @@ -2789,6 +2789,35 @@ public function enterAnonymousFunction( } public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self + { + $anonymousFunctionReflection = $this->getType($arrowFunction); + if (!$anonymousFunctionReflection instanceof ClosureType) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $scope = $this->enterArrowFunctionWithoutReflection($arrowFunction); + + return $this->scopeFactory->create( + $scope->context, + $scope->isDeclareStrictTypes(), + $scope->constantTypes, + $scope->getFunction(), + $scope->getNamespace(), + $scope->variableTypes, + $scope->moreSpecificTypes, + $scope->conditionalExpressions, + $scope->inClosureBindScopeClass, + $anonymousFunctionReflection, + true, + [], + [], + [], + $scope->afterExtractCall, + $scope->parentScope + ); + } + + private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFunction): self { $variableTypes = $this->variableTypes; $mixed = new MixedType(); @@ -2813,11 +2842,6 @@ public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self unset($variableTypes['this']); } - $anonymousFunctionReflection = $this->getType($arrowFunction); - if (!$anonymousFunctionReflection instanceof ClosureType) { - throw new \PHPStan\ShouldNotHappenException(); - } - $conditionalExpressions = []; foreach ($this->conditionalExpressions as $conditionalExprString => $holders) { $newHolders = []; @@ -2883,7 +2907,7 @@ public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self $this->moreSpecificTypes, $conditionalExpressions, $this->inClosureBindScopeClass, - $anonymousFunctionReflection, + null, true, [], [], diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 607b434843..e96637b213 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -10594,6 +10594,15 @@ public function dataBug4188(): array return $this->gatherAssertTypes(__DIR__ . '/data/bug-4188.php'); } + public function dataBug4339(): array + { + if (PHP_VERSION_ID < 70400) { + return []; + } + + return $this->gatherAssertTypes(__DIR__ . '/data/bug-4339.php'); + } + /** * @param string $file * @return array @@ -10797,6 +10806,7 @@ private function gatherAssertTypes(string $file): array * @dataProvider dataBug2980 * @dataProvider dataBug3986 * @dataProvider dataBug4188 + * @dataProvider dataBug4339 * @param string $assertType * @param string $file * @param mixed ...$args diff --git a/tests/PHPStan/Analyser/data/bug-4339.php b/tests/PHPStan/Analyser/data/bug-4339.php new file mode 100644 index 0000000000..371b37900b --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4339.php @@ -0,0 +1,15 @@ += 7.4 + +namespace Bug4339; + +use function PHPStan\Analyser\assertType; + +function (?string $v) { + assertType('string', $v ?? '-'); + fn (?string $value): string => assertType('string', $value ?? '-'); + fn (?string $value): void => assertType('string|null', $value); + + $f = fn (?string $value): string => $value ?? '-'; + + assertType('string', $f($v)); +}; diff --git a/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php b/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php index 455566c3cd..1ff3081dc5 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php @@ -27,6 +27,7 @@ public function doFoo(): void public function doBar(): void { fn () => $this->doFoo(); + fn (?string $value): string => $value ?? '-'; } }