88use PhpParser \Node \Expr \ArrowFunction ;
99use PhpParser \Node \Expr \Assign ;
1010use PhpParser \Node \Expr \Closure ;
11+ use PhpParser \Node \Expr \FuncCall ;
1112use PhpParser \Node \Expr \Variable ;
1213use PhpParser \Node \FunctionLike ;
14+ use PhpParser \Node \Name ;
1315use PhpParser \Node \Scalar \String_ ;
1416use PHPStan \Analyser \Scope ;
17+ use PHPStan \Broker \Broker ;
1518use PHPStan \Rules \Rule ;
1619use PHPStan \Rules \RuleError ;
1720use PHPStan \Rules \RuleErrorBuilder ;
@@ -33,6 +36,10 @@ final class UnusedVariableRule implements Rule
3336 '_SESSION ' => true ,
3437 ];
3538
39+ public function __construct (
40+ private readonly Broker $ broker ,
41+ ) {}
42+
3643 public function getNodeType (): string
3744 {
3845 return FunctionLike::class;
@@ -60,7 +67,7 @@ public function processNode(Node $node, Scope $scope): array
6067
6168 $ unusedVariables = [];
6269 $ usedVariables = [];
63- $ this ->gatherVariablesUsage ($ node , $ unusedVariables , $ usedVariables , $ parameters , $ node );
70+ $ this ->gatherVariablesUsage ($ node , $ scope , $ unusedVariables , $ usedVariables , $ parameters , $ node );
6471
6572 foreach ($ unusedVariables as $ varName => $ var ) {
6673 if (! isset ($ usedVariables [$ varName ])) {
@@ -82,7 +89,7 @@ public function processNode(Node $node, Scope $scope): array
8289 * @param bool[] $usedVariables
8390 * @param mixed[] $parameters
8491 */
85- private function gatherVariablesUsage (Node $ node , array & $ unusedVariables , array & $ usedVariables , array $ parameters = [], ?Node $ originalNode = null ): void
92+ private function gatherVariablesUsage (Node $ node , Scope $ scope , array & $ unusedVariables , array & $ usedVariables , array $ parameters = [], ?Node $ originalNode = null ): void
8693 {
8794 if ($ node instanceof FunctionLike
8895 && $ node !== $ originalNode
@@ -98,20 +105,23 @@ private function gatherVariablesUsage(Node $node, array & $unusedVariables, arra
98105
99106 return ;
100107 }
108+
109+ $ this ->captureVariablesUsedByCompactFunction ($ node , $ scope , $ usedVariables );
110+
101111 if ($ node instanceof Assign) {
102112 if ($ node ->var instanceof Variable) {
103113 if (\is_string ($ node ->var ->name ) && ! isset ($ parameters [$ node ->var ->name ]) && ! isset (self ::GLOBAL_VARIABLES [$ node ->var ->name ])) {
104114 $ unusedVariables [$ node ->var ->name ] = $ node ->var ;
105115 }
106116 } else {
107117 if (\property_exists ($ node ->var , 'var ' ) && $ node ->var ->var instanceof Node) {
108- $ this ->gatherVariablesUsage ($ node ->var ->var , $ unusedVariables , $ usedVariables , $ parameters );
118+ $ this ->gatherVariablesUsage ($ node ->var ->var , $ scope , $ unusedVariables , $ usedVariables , $ parameters );
109119 }
110120 if (\property_exists ($ node ->var , 'dim ' ) && $ node ->var ->dim instanceof Node) {
111- $ this ->gatherVariablesUsage ($ node ->var ->dim , $ unusedVariables , $ usedVariables , $ parameters );
121+ $ this ->gatherVariablesUsage ($ node ->var ->dim , $ scope , $ unusedVariables , $ usedVariables , $ parameters );
112122 }
113123 if (\property_exists ($ node ->var , 'name ' ) && $ node ->var ->name instanceof Node) {
114- $ this ->gatherVariablesUsage ($ node ->var ->name , $ unusedVariables , $ usedVariables , $ parameters );
124+ $ this ->gatherVariablesUsage ($ node ->var ->name , $ scope , $ unusedVariables , $ usedVariables , $ parameters );
115125 }
116126 }
117127 }
@@ -121,7 +131,7 @@ private function gatherVariablesUsage(Node $node, array & $unusedVariables, arra
121131 } elseif ($ node ->name instanceof String_) {
122132 $ usedVariables [$ node ->name ->value ] = true ;
123133 } else {
124- $ this ->gatherVariablesUsage ($ node ->name , $ unusedVariables , $ usedVariables , $ parameters );
134+ $ this ->gatherVariablesUsage ($ node ->name , $ scope , $ unusedVariables , $ usedVariables , $ parameters );
125135 }
126136 }
127137
@@ -130,14 +140,44 @@ private function gatherVariablesUsage(Node $node, array & $unusedVariables, arra
130140 continue ;
131141 }
132142 if ($ node ->{$ nodeName } instanceof Node) {
133- $ this ->gatherVariablesUsage ($ node ->{$ nodeName }, $ unusedVariables , $ usedVariables , $ parameters );
143+ $ this ->gatherVariablesUsage ($ node ->{$ nodeName }, $ scope , $ unusedVariables , $ usedVariables , $ parameters );
134144 } elseif (\is_iterable ($ node ->{$ nodeName })) {
135145 foreach ($ node ->{$ nodeName } as $ subNode ) {
136146 if ($ subNode instanceof Node) {
137- $ this ->gatherVariablesUsage ($ subNode , $ unusedVariables , $ usedVariables , $ parameters );
147+ $ this ->gatherVariablesUsage ($ subNode , $ scope , $ unusedVariables , $ usedVariables , $ parameters );
138148 }
139149 }
140150 }
141151 }
142152 }
153+
154+ /** @param bool[] $usedVariables */
155+ private function captureVariablesUsedByCompactFunction (Node $ node , Scope $ scope , array & $ usedVariables ): void
156+ {
157+ if (! $ this ->isCompactFunction ($ node , $ scope )) {
158+ return ;
159+ }
160+ // @phpstan-ignore-next-line: $node->args is valid due to $node being instanceof FuncCall
161+ foreach ($ node ->args as $ arg ) {
162+ if ($ arg ->value instanceof String_) {
163+ $ usedVariables [$ arg ->value ->value ] = true ;
164+ }
165+ }
166+ }
167+
168+ private function isCompactFunction (Node $ node , Scope $ scope ): bool
169+ {
170+ if (! $ node instanceof FuncCall) {
171+ return false ;
172+ }
173+ if (! $ node ->name instanceof Name) {
174+ return false ;
175+ }
176+ if (! $ this ->broker ->hasFunction ($ node ->name , $ scope )) {
177+ return false ;
178+ }
179+ $ calledFunctionName = $ this ->broker ->resolveFunctionName ($ node ->name , $ scope );
180+
181+ return 'compact ' === $ calledFunctionName ;
182+ }
143183}
0 commit comments