1313use PhpParser \Node \Expr \FuncCall ;
1414use PhpParser \Node \Expr \Include_ ;
1515use PhpParser \Node \Expr \MethodCall ;
16- use PhpParser \Node \Expr \New_ ;
1716use PhpParser \Node \Expr \NullsafeMethodCall ;
1817use PhpParser \Node \Expr \StaticCall ;
1918use PhpParser \Node \Expr \Variable ;
19+ use PhpParser \Node \Name ;
2020use PhpParser \Node \Stmt ;
2121use PhpParser \Node \Stmt \Class_ ;
2222use PhpParser \Node \Stmt \ClassMethod ;
2323use PhpParser \Node \Stmt \Expression ;
2424use PhpParser \Node \Stmt \Function_ ;
2525use PhpParser \NodeVisitor ;
26+ use PHPStan \Reflection \AttributeReflection ;
2627use PHPStan \Reflection \ClassReflection ;
28+ use PHPStan \Reflection \ReflectionProvider ;
2729use PHPStan \Type \ObjectType ;
2830use Rector \DeadCode \SideEffect \SideEffectNodeDetector ;
2931use Rector \NodeAnalyzer \VariableAnalyzer ;
3032use Rector \NodeManipulator \StmtsManipulator ;
3133use Rector \Php \ReservedKeywordAnalyzer ;
32- use Rector \Php80 \NodeAnalyzer \PhpAttributeAnalyzer ;
33- use Rector \PhpParser \AstResolver ;
3434use Rector \PhpParser \Enum \NodeGroup ;
3535use Rector \PhpParser \Node \BetterNodeFinder ;
36+ use Rector \PHPStan \ScopeFetcher ;
3637use Rector \Rector \AbstractRector ;
3738use Rector \ValueObject \MethodName ;
3839use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
@@ -49,8 +50,7 @@ public function __construct(
4950 private readonly VariableAnalyzer $ variableAnalyzer ,
5051 private readonly BetterNodeFinder $ betterNodeFinder ,
5152 private readonly StmtsManipulator $ stmtsManipulator ,
52- private readonly AstResolver $ astResolver ,
53- private readonly PhpAttributeAnalyzer $ phpAttributeAnalyzer
53+ private readonly ReflectionProvider $ reflectionProvider ,
5454 ) {
5555 }
5656
@@ -273,16 +273,8 @@ function (Node $subNode) use (&$refVariableNames) {
273273 continue ;
274274 }
275275
276- if ($ assign ->expr instanceof FuncCall
277- || $ assign ->expr instanceof StaticCall
278- || $ assign ->expr instanceof MethodCall
279- || $ assign ->expr instanceof New_
280- || $ assign ->expr instanceof NullsafeMethodCall) {
281- $ targetCall = $ this ->astResolver ->resolveClassMethodOrFunctionFromCall ($ assign ->expr );
282- if (($ targetCall instanceof ClassMethod || $ targetCall instanceof Function_)
283- && $ this ->phpAttributeAnalyzer ->hasPhpAttribute ($ targetCall , 'NoDiscard ' )) {
284- continue ;
285- }
276+ if ($ this ->isNoDiscardCall ($ assign ->expr )) {
277+ continue ;
286278 }
287279
288280 $ assignedVariableNamesByStmtPosition [$ key ] = $ variableName ;
@@ -306,4 +298,71 @@ private function shouldSkipVariable(Variable $variable, string $variableName, ar
306298
307299 return in_array ($ variableName , $ refVariableNames , true );
308300 }
301+
302+ private function isNoDiscardCall (Expr $ expr ): bool
303+ {
304+ if ($ expr instanceof FuncCall) {
305+ if (! $ expr ->name instanceof Name) {
306+ return false ;
307+ }
308+
309+ $ scope = ScopeFetcher::fetch ($ expr );
310+
311+ if (! $ this ->reflectionProvider ->hasFunction ($ expr ->name , $ scope )) {
312+ return false ;
313+ }
314+
315+ return $ this ->hasNoDiscardAttribute (
316+ $ this ->reflectionProvider ->getFunction ($ expr ->name , $ scope )
317+ ->getAttributes ()
318+ );
319+ }
320+
321+ if ($ expr instanceof StaticCall) {
322+ $ classNames = $ this ->getType ($ expr ->class )
323+ ->getObjectClassNames ();
324+ $ methodName = $ this ->getName ($ expr ->name );
325+ } elseif ($ expr instanceof MethodCall || $ expr instanceof NullsafeMethodCall) {
326+ $ classNames = $ this ->getType ($ expr ->var )
327+ ->getObjectClassNames ();
328+ $ methodName = $ this ->getName ($ expr ->name );
329+ } else {
330+ return false ;
331+ }
332+
333+ if ($ classNames === [] || $ methodName === null ) {
334+ return false ;
335+ }
336+
337+ foreach ($ classNames as $ className ) {
338+ if (! $ this ->reflectionProvider ->hasClass ($ className )) {
339+ continue ;
340+ }
341+
342+ $ classReflection = $ this ->reflectionProvider ->getClass ($ className );
343+ if (! $ classReflection ->hasNativeMethod ($ methodName )) {
344+ continue ;
345+ }
346+
347+ if ($ this ->hasNoDiscardAttribute ($ classReflection ->getNativeMethod ($ methodName )->getAttributes ())) {
348+ return true ;
349+ }
350+ }
351+
352+ return false ;
353+ }
354+
355+ /**
356+ * @param AttributeReflection[] $attributes
357+ */
358+ private function hasNoDiscardAttribute (array $ attributes ): bool
359+ {
360+ foreach ($ attributes as $ attribute ) {
361+ if ($ attribute ->getName () === 'NoDiscard ' ) {
362+ return true ;
363+ }
364+ }
365+
366+ return false ;
367+ }
309368}
0 commit comments