From 09553953bacc9cc1d17b8c266fe058207e754243 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Thu, 19 Mar 2026 07:06:54 +0000 Subject: [PATCH] Fix phpstan/phpstan#14325: Apply @param PHPDoc types to closure parameters - Added resolveClosurePhpDocParameterTypes() to extract @param types from PHPDoc comments on the statement containing a closure assignment - Applied resolved PHPDoc parameter types to the closure scope in processClosureNode() - Updated count-type test expectations to reflect correct generic types - New regression test in tests/PHPStan/Analyser/nsrt/bug-14325.php --- src/Analyser/NodeScopeResolver.php | 46 ++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-14325.php | 45 +++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/count-type.php | 4 +- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-14325.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8935ade8e2..2c0d8f0f09 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2716,7 +2716,16 @@ public function processClosureNode( $this->callNodeCallback($nodeCallback, $expr->returnType, $scope, $storage); } + $phpDocParameterTypes = $this->resolveClosurePhpDocParameterTypes($stmt, $scope, $expr); + $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); + foreach ($phpDocParameterTypes as $paramName => $paramType) { + if (!$closureScope->hasVariableType($paramName)->yes()) { + continue; + } + + $closureScope = $closureScope->assignVariable($paramName, $paramType, $closureScope->getNativeType(new Variable($paramName)), TrinaryLogic::createYes()); + } $closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); $closureType = $closureScope->getAnonymousFunctionReflection(); if (!$closureType instanceof ClosureType) { @@ -4175,6 +4184,43 @@ private function processNodesForCalledMethod($node, ExpressionResultStorage $sto } } + /** + * @return array + */ + private function resolveClosurePhpDocParameterTypes(Node\Stmt $stmt, MutatingScope $scope, Expr\Closure $closure): array + { + $phpDocParameterTypes = []; + + foreach ($stmt->getComments() as $comment) { + if (!$comment instanceof Doc) { + continue; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $scope->getFunction() !== null ? $scope->getFunction()->getName() : null, + $comment->getText(), + ); + + foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { + foreach ($closure->params as $param) { + if ( + $param->var instanceof Variable + && is_string($param->var->name) + && $param->var->name === $paramName + ) { + $phpDocParameterTypes[$paramName] = $paramTag->getType(); + break; + } + } + } + } + + return $phpDocParameterTypes; + } + /** * @return array{TemplateTypeMap, array, array, array, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array, array<(string|int), VarTag>, bool, ?ResolvedPhpDocBlock} */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-14325.php b/tests/PHPStan/Analyser/nsrt/bug-14325.php new file mode 100644 index 0000000000..ad3860bce3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14325.php @@ -0,0 +1,45 @@ + $array + */ +function func(array $array): void +{ + assertType('list', $array); + $array[] = 'bar'; + assertType('non-empty-list', $array); +} + +/** + * @param list $array + */ +$func = function(array $array): void +{ + assertType('list', $array); + $array[] = 'bar'; + assertType('non-empty-list', $array); +}; + +class Foo +{ + /** + * @param list $array + */ + public function method(array $array): void + { + assertType('list', $array); + + /** + * @param list $inner + */ + $closure = function (array $inner): void { + assertType('list', $inner); + $inner[] = 'baz'; + assertType('non-empty-list', $inner); + }; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/count-type.php b/tests/PHPStan/Analyser/nsrt/count-type.php index 8ebe0c445e..bf7d1ac29d 100644 --- a/tests/PHPStan/Analyser/nsrt/count-type.php +++ b/tests/PHPStan/Analyser/nsrt/count-type.php @@ -104,11 +104,11 @@ public function constantArrayWhichCanBecomeList(string $h): void */ function(\ArrayObject $obj): void { if (count($obj) === 0) { - assertType('ArrayObject', $obj); + assertType('ArrayObject', $obj); return; } - assertType('ArrayObject', $obj); + assertType('ArrayObject', $obj); }; function($mixed): void {