From e1b48d4141903f83e36a361b78e2ecf0d50f203a Mon Sep 17 00:00:00 2001 From: QDenka Date: Sun, 8 Feb 2026 09:35:06 +0000 Subject: [PATCH] Skip ClosureToArrowFunctionRector when closure has @var docblock Arrow functions do not support inline @var annotations for type narrowing. Previously, the rule only skipped conversion when the @var type was more specific than the native type. However, type comparison can fail with generics (e.g. Builder vs Builder), causing unintended conversion that breaks PHPStan type narrowing. Now the rule skips conversion whenever a @var tag is present on the return statement, regardless of type comparison. Fixes rectorphp/rector#9637 --- ..._closure_with_var_docblock_generic.php.inc | 16 ++++++++++ .../Fixture/with_equal_var_doc_type.php.inc | 15 ---------- .../ClosureArrowFunctionAnalyzer.php | 29 ++----------------- 3 files changed, 19 insertions(+), 41 deletions(-) create mode 100644 rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_closure_with_var_docblock_generic.php.inc diff --git a/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_closure_with_var_docblock_generic.php.inc b/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_closure_with_var_docblock_generic.php.inc new file mode 100644 index 00000000000..7fcb85b2eba --- /dev/null +++ b/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_closure_with_var_docblock_generic.php.inc @@ -0,0 +1,16 @@ + $query */ + return $query->count(); + }; + } +} + +?> diff --git a/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/with_equal_var_doc_type.php.inc b/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/with_equal_var_doc_type.php.inc index d21f587edd5..27e647dd63a 100644 --- a/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/with_equal_var_doc_type.php.inc +++ b/rules-tests/Php74/Rector/Closure/ClosureToArrowFunctionRector/Fixture/with_equal_var_doc_type.php.inc @@ -14,18 +14,3 @@ class WithEqualVarDocType } ?> ------ - $var; - } -} - -?> diff --git a/rules/Php74/NodeAnalyzer/ClosureArrowFunctionAnalyzer.php b/rules/Php74/NodeAnalyzer/ClosureArrowFunctionAnalyzer.php index d6b4ea2c86c..c1687c5d592 100644 --- a/rules/Php74/NodeAnalyzer/ClosureArrowFunctionAnalyzer.php +++ b/rules/Php74/NodeAnalyzer/ClosureArrowFunctionAnalyzer.php @@ -12,11 +12,9 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\Return_; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; -use PHPStan\Type\MixedType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\NodeAnalyzer\CompactFuncCallAnalyzer; -use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\PhpParser\Comparing\NodeComparator; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Util\ArrayChecker; @@ -28,7 +26,6 @@ public function __construct( private NodeComparator $nodeComparator, private ArrayChecker $arrayChecker, private PhpDocInfoFactory $phpDocInfoFactory, - private NodeTypeResolver $nodeTypeResolver, private CompactFuncCallAnalyzer $compactFuncCallAnalyzer ) { } @@ -92,7 +89,8 @@ function (Node $node) use ($variables): bool { } /** - * Ensure @var doc usage with more specific type on purpose to be skipped + * Ensure @var doc usage to be skipped, as arrow functions do not support + * inline @var annotations for type narrowing (e.g. generic types like Builder) */ private function shouldSkipMoreSpecificTypeWithVarDoc(Return_ $return, Expr $expr): bool { @@ -103,29 +101,8 @@ private function shouldSkipMoreSpecificTypeWithVarDoc(Return_ $return, Expr $exp } $varTagValueNode = $phpDocInfo->getVarTagValueNode(); - if (! $varTagValueNode instanceof VarTagValueNode) { - return false; - } - - $varType = $phpDocInfo->getVarType(); - if ($varType instanceof MixedType) { - return false; - } - - $variableName = ltrim($varTagValueNode->variableName, '$'); - $variable = $this->betterNodeFinder->findFirst( - $expr, - static fn (Node $node): bool => $node instanceof Variable && $node->name === $variableName - ); - - if (! $variable instanceof Variable) { - return false; - } - - $nativeVariableType = $this->nodeTypeResolver->getNativeType($variable); - // not equal with native type means more specific type - return ! $nativeVariableType->equals($varType); + return $varTagValueNode instanceof VarTagValueNode; } private function shouldSkipForUsedReferencedValue(Closure $closure): bool