88use PhpParser \Node \Expr \ArrowFunction ;
99use PhpParser \Node \Expr \Closure ;
1010use PhpParser \Node \Expr \FuncCall ;
11+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
1112use PHPStan \Type \ArrayType ;
1213use PHPStan \Type \Constant \ConstantArrayType ;
14+ use PHPStan \Type \IntersectionType ;
1315use PHPStan \Type \MixedType ;
16+ use PHPStan \Type \Type ;
1417use Rector \PHPStanStaticTypeMapper \Enum \TypeKind ;
1518use Rector \Rector \AbstractRector ;
1619use Rector \StaticTypeMapper \StaticTypeMapper ;
2629final class AddArrayFunctionClosureParamTypeRector extends AbstractRector implements MinPhpVersionInterface
2730{
2831 public function __construct (
29- private readonly StaticTypeMapper $ staticTypeMapper
32+ private readonly StaticTypeMapper $ staticTypeMapper,
3033 ) {
3134 }
3235
@@ -73,6 +76,8 @@ public function refactor(Node $node): ?Node
7376 return null ;
7477 }
7578
79+ $ hasChanged = false ;
80+
7681 foreach (NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS as $ functionName => $ positions ) {
7782 if (! $ this ->isName ($ node , $ functionName )) {
7883 continue ;
@@ -96,27 +101,29 @@ public function refactor(Node $node): ?Node
96101 }
97102
98103 $ passedExprType = $ this ->getType ($ node ->getArgs ()[$ arrayPosition ]->value );
99- if ($ passedExprType instanceof ConstantArrayType || $ passedExprType instanceof ArrayType) {
100- $ singlePassedExprType = $ passedExprType ->getItemType ();
101-
102- if ($ singlePassedExprType instanceof MixedType) {
103- continue ;
104- }
105104
106- $ paramType = $ this ->staticTypeMapper ->mapPHPStanTypeToPhpParserNode (
107- $ singlePassedExprType ,
108- TypeKind::PARAM
109- );
110-
111- if (! $ paramType instanceof Node) {
112- continue ;
113- }
105+ $ singlePassedExprType = $ this ->resolveArrayItemType ($ passedExprType );
106+ if (! $ singlePassedExprType instanceof Type) {
107+ continue ;
108+ }
109+ if ($ singlePassedExprType instanceof MixedType) {
110+ continue ;
111+ }
114112
115- $ arrowFunctionParam ->type = $ paramType ;
113+ $ paramType = $ this ->staticTypeMapper ->mapPHPStanTypeToPhpParserNode (
114+ $ singlePassedExprType ,
115+ TypeKind::PARAM
116+ );
116117
117- return $ node ;
118+ if (! $ paramType instanceof Node) {
119+ continue ;
118120 }
119121
122+ $ hasChanged = true ;
123+ $ arrowFunctionParam ->type = $ paramType ;
124+ }
125+
126+ if ($ hasChanged === false ) {
120127 return null ;
121128 }
122129
@@ -127,4 +134,31 @@ public function provideMinPhpVersion(): int
127134 {
128135 return PhpVersionFeature::SCALAR_TYPES ;
129136 }
137+
138+ private function resolveArrayItemType (Type $ mainType ): ?Type
139+ {
140+ if ($ mainType instanceof ConstantArrayType || $ mainType instanceof ArrayType) {
141+ return $ mainType ->getItemType ();
142+ }
143+
144+ if ($ mainType instanceof IntersectionType) {
145+ foreach ($ mainType ->getTypes () as $ subType ) {
146+ if ($ subType instanceof AccessoryArrayListType) {
147+ continue ;
148+ }
149+
150+ if (! $ subType ->isArray ()->yes ()) {
151+ continue ;
152+ }
153+
154+ if (! $ subType instanceof ArrayType) {
155+ continue ;
156+ }
157+
158+ return $ subType ->getItemType ();
159+ }
160+ }
161+
162+ return null ;
163+ }
130164}
0 commit comments