44
55use Override ;
66use PhpParser \Node ;
7+ use PhpParser \Node \Expr ;
78use PhpParser \Node \Identifier ;
9+ use PhpParser \Node \Name ;
810use PhpParser \NodeVisitorAbstract ;
911use PHPStan \DependencyInjection \AutowiredService ;
12+ use function array_key_exists ;
13+ use function array_shift ;
14+ use function array_unshift ;
1015use function count ;
1116
1217#[AutowiredService]
@@ -15,6 +20,11 @@ final class ClosureBindArgVisitor extends NodeVisitorAbstract
1520
1621 public const ATTRIBUTE_NAME = 'closureBindArg ' ;
1722
23+ public const SCOPE_ATTRIBUTE_NAME = 'closureBindScope ' ;
24+
25+ /** @var list<?Expr> */
26+ private array $ scopeStack = [];
27+
1828 #[Override]
1929 public function enterNode (Node $ node ): ?Node
2030 {
@@ -30,7 +40,35 @@ public function enterNode(Node $node): ?Node
3040 if (count ($ args ) > 1 ) {
3141 $ args [0 ]->setAttribute (self ::ATTRIBUTE_NAME , true );
3242 }
43+
44+ // null means default scope "static"
45+ array_unshift ($ this ->scopeStack , $ args [2 ]->value ?? null );
46+ }
47+
48+ if ($ node instanceof Name
49+ && array_key_exists (0 , $ this ->scopeStack )
50+ && $ node ->isSpecialClassName ()
51+ ) {
52+ $ node ->setAttribute (self ::SCOPE_ATTRIBUTE_NAME , $ this ->scopeStack [0 ]);
53+ }
54+
55+ return null ;
56+ }
57+
58+ #[Override]
59+ public function leaveNode (Node $ node ): ?Node
60+ {
61+ if (
62+ $ node instanceof Node \Expr \StaticCall
63+ && $ node ->class instanceof Node \Name
64+ && $ node ->class ->toLowerString () === 'closure '
65+ && $ node ->name instanceof Identifier
66+ && $ node ->name ->toLowerString () === 'bind '
67+ && !$ node ->isFirstClassCallable ()
68+ ) {
69+ array_shift ($ this ->scopeStack );
3370 }
71+
3472 return null ;
3573 }
3674
0 commit comments