From eb75944eb075b8c51605ff443ead2258dbc4a3f8 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 24 Oct 2026 15:29:16 +0700 Subject: [PATCH 1/2] Add failing test case for Attribute Closure --- ...bute_on_closure_and_arrow_function.php.inc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 rules-tests/Php55/Rector/String_/StringClassNameToClassConstantRector/Fixture/attribute_on_closure_and_arrow_function.php.inc diff --git a/rules-tests/Php55/Rector/String_/StringClassNameToClassConstantRector/Fixture/attribute_on_closure_and_arrow_function.php.inc b/rules-tests/Php55/Rector/String_/StringClassNameToClassConstantRector/Fixture/attribute_on_closure_and_arrow_function.php.inc new file mode 100644 index 00000000000..40f9989229c --- /dev/null +++ b/rules-tests/Php55/Rector/String_/StringClassNameToClassConstantRector/Fixture/attribute_on_closure_and_arrow_function.php.inc @@ -0,0 +1,33 @@ + 1); + } +} + +?> +----- + 1); + } +} + +?> From 62bbf5485f0acbd0ef3c284ba5d014d05fc6f416 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 24 Oct 2026 17:30:55 +0700 Subject: [PATCH 2/2] fix with set closure/arrowfunction attribute scope --- .../Scope/PHPStanNodeScopeResolver.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php b/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php index f8187c53399..e915a0df092 100644 --- a/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php +++ b/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php @@ -10,6 +10,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayDimFetch; +use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignOp; use PhpParser\Node\Expr\AssignRef; @@ -20,6 +21,7 @@ use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\Clone_; +use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\Empty_; use PhpParser\Node\Expr\ErrorSuppress; @@ -378,6 +380,11 @@ private function fillScope(array $stmts, string $filePath, MutatingScope $scope) return; } + + // PHPStan does not process attributes of closures and arrow functions + if (($node instanceof Closure || $node instanceof ArrowFunction) && $node->attrGroups !== []) { + $this->processClosureAttributeGroups($node, $mutatingScope, $nodeCallback); + } }; try { @@ -416,6 +423,32 @@ private function processIssetOrUnset(Isset_|Unset_ $node, MutatingScope $mutatin } } + /** + * @param callable(Node $node, MutatingScope $scope): void $nodeCallback + */ + private function processClosureAttributeGroups( + Closure|ArrowFunction $node, + MutatingScope $mutatingScope, + callable $nodeCallback + ): void { + $argExpressions = []; + foreach ($node->attrGroups as $attrGroup) { + $attrGroup->setAttribute(AttributeKey::SCOPE, $mutatingScope); + + foreach ($attrGroup->attrs as $attr) { + $attr->setAttribute(AttributeKey::SCOPE, $mutatingScope); + $attr->name->setAttribute(AttributeKey::SCOPE, $mutatingScope); + + foreach ($attr->args as $arg) { + $arg->setAttribute(AttributeKey::SCOPE, $mutatingScope); + $argExpressions[] = new Expression($arg->value); + } + } + } + + $this->nodeScopeResolverProcessNodes($argExpressions, $mutatingScope, $nodeCallback); + } + private function processEcho(Echo_ $echo, MutatingScope $mutatingScope): void { foreach ($echo->exprs as $expr) {