1010use PhpParser \Node \Expr \BinaryOp \Concat ;
1111use PhpParser \Node \Expr \BinaryOp \Identical ;
1212use PhpParser \Node \Expr \BooleanNot ;
13+ use PhpParser \Node \Expr \CallLike ;
1314use PhpParser \Node \Expr \Cast ;
1415use PhpParser \Node \Expr \Cast \String_ ;
1516use PhpParser \Node \Expr \Empty_ ;
1617use PhpParser \Node \Expr \FuncCall ;
1718use PhpParser \Node \Expr \Ternary ;
19+ use PHPStan \Reflection \FunctionReflection ;
20+ use PHPStan \Reflection \MethodReflection ;
1821use PHPStan \Type \Constant \ConstantIntegerType ;
22+ use Rector \NodeTypeResolver \PHPStan \ParametersAcceptorSelectorVariantsWrapper ;
1923use Rector \PhpParser \Node \Value \ValueResolver ;
24+ use Rector \PHPStan \ScopeFetcher ;
2025use Rector \Rector \AbstractRector ;
26+ use Rector \Reflection \ReflectionResolver ;
2127use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
2228use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
2329
@@ -34,6 +40,7 @@ final class DowngradeSubstrFalsyRector extends AbstractRector
3440 private const IS_UNCASTABLE = 'is_uncastable ' ;
3541
3642 public function __construct (
43+ private readonly ReflectionResolver $ reflectionResolver ,
3744 private readonly ValueResolver $ valueResolver
3845 ) {
3946
@@ -58,12 +65,13 @@ public function getNodeTypes(): array
5865 Ternary::class,
5966 Identical::class,
6067 Concat::class,
68+ CallLike::class,
6169 FuncCall::class,
6270 ];
6371 }
6472
6573 /**
66- * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|FuncCall $node
74+ * @param Cast|Empty_|BooleanNot|Ternary|Identical|Concat|CallLike| FuncCall $node
6775 */
6876 public function refactor (Node $ node ): ?Node
6977 {
@@ -98,6 +106,35 @@ public function refactor(Node $node): ?Node
98106 return null ;
99107 }
100108
109+ if ($ node instanceof CallLike) {
110+ if ($ node ->isFirstClassCallable ()) {
111+ return null ;
112+ }
113+
114+ $ reflection = $ this ->reflectionResolver ->resolveFunctionLikeReflectionFromCall ($ node );
115+
116+ if (! $ reflection instanceof MethodReflection && ! $ reflection instanceof FunctionReflection) {
117+ return null ;
118+ }
119+
120+ $ parameterAcceptor = ParametersAcceptorSelectorVariantsWrapper::select (
121+ $ reflection ,
122+ $ node ,
123+ ScopeFetcher::fetch ($ node )
124+ );
125+
126+ foreach ($ parameterAcceptor ->getParameters () as $ position => $ parameterReflection ) {
127+ if ($ parameterReflection ->getType ()->isFalse ()->no ()) {
128+ continue ;
129+ }
130+
131+ $ arg = $ node ->getArg ($ parameterReflection ->getName (), $ position );
132+ if ($ arg instanceof Arg) {
133+ $ arg ->value ->setAttribute (self ::IS_UNCASTABLE , true );
134+ }
135+ }
136+ }
137+
101138 if (! $ this ->isName ($ node , 'substr ' )) {
102139 return null ;
103140 }
0 commit comments