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,73 @@ 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+ $ name = $ this ->getName ($ expr );
306+
307+ if ($ name === null ) {
308+ return false ;
309+ }
310+
311+ $ scope = ScopeFetcher::fetch ($ expr );
312+
313+ if (! $ this ->reflectionProvider ->hasFunction (new Name ($ name ), $ scope )) {
314+ return false ;
315+ }
316+
317+ return $ this ->hasNoDiscardAttribute (
318+ $ this ->reflectionProvider ->getFunction (new Name ($ name ), $ scope )
319+ ->getAttributes ()
320+ );
321+ }
322+
323+ if ($ expr instanceof StaticCall) {
324+ $ classNames = $ this ->getType ($ expr ->class )
325+ ->getObjectClassNames ();
326+ $ methodName = $ this ->getName ($ expr ->name );
327+ } elseif ($ expr instanceof MethodCall || $ expr instanceof NullsafeMethodCall) {
328+ $ classNames = $ this ->getType ($ expr ->var )
329+ ->getObjectClassNames ();
330+ $ methodName = $ this ->getName ($ expr ->name );
331+ } else {
332+ return false ;
333+ }
334+
335+ if ($ classNames === [] || $ methodName === null ) {
336+ return false ;
337+ }
338+
339+ foreach ($ classNames as $ className ) {
340+ if (! $ this ->reflectionProvider ->hasClass ($ className )) {
341+ continue ;
342+ }
343+
344+ $ classReflection = $ this ->reflectionProvider ->getClass ($ className );
345+ if (! $ classReflection ->hasNativeMethod ($ methodName )) {
346+ continue ;
347+ }
348+
349+ if ($ this ->hasNoDiscardAttribute ($ classReflection ->getNativeMethod ($ methodName )->getAttributes ())) {
350+ return true ;
351+ }
352+ }
353+
354+ return false ;
355+ }
356+
357+ /**
358+ * @param AttributeReflection[] $attributes
359+ */
360+ private function hasNoDiscardAttribute (array $ attributes ): bool
361+ {
362+ foreach ($ attributes as $ attribute ) {
363+ if ($ attribute ->getName () === 'NoDiscard ' ) {
364+ return true ;
365+ }
366+ }
367+
368+ return false ;
369+ }
309370}
0 commit comments