2
2
3
3
namespace PHPStan \Type \Php ;
4
4
5
- use PhpParser \Node \Arg ;
6
- use PhpParser \Node \Expr ;
7
- use PhpParser \Node \Expr \ArrowFunction ;
8
- use PhpParser \Node \Expr \Closure ;
9
- use PhpParser \Node \Expr \Error ;
10
5
use PhpParser \Node \Expr \FuncCall ;
11
- use PhpParser \Node \Expr \MethodCall ;
12
- use PhpParser \Node \Expr \StaticCall ;
13
- use PhpParser \Node \Expr \Variable ;
14
- use PhpParser \Node \Name ;
15
- use PhpParser \Node \Stmt \Return_ ;
16
- use PHPStan \Analyser \MutatingScope ;
17
6
use PHPStan \Analyser \Scope ;
18
7
use PHPStan \Reflection \FunctionReflection ;
19
- use PHPStan \Reflection \ReflectionProvider ;
20
- use PHPStan \ShouldNotHappenException ;
21
- use PHPStan \Type \ArrayType ;
22
- use PHPStan \Type \BenevolentUnionType ;
23
- use PHPStan \Type \Constant \ConstantArrayType ;
24
- use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
25
- use PHPStan \Type \Constant \ConstantBooleanType ;
26
- use PHPStan \Type \Constant \ConstantIntegerType ;
27
8
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
28
- use PHPStan \Type \ErrorType ;
29
- use PHPStan \Type \MixedType ;
30
- use PHPStan \Type \NeverType ;
31
- use PHPStan \Type \NullType ;
32
- use PHPStan \Type \StaticTypeFactory ;
33
9
use PHPStan \Type \Type ;
34
- use PHPStan \Type \TypeCombinator ;
35
- use PHPStan \Type \TypeUtils ;
36
- use function array_map ;
37
- use function count ;
38
- use function in_array ;
39
- use function is_string ;
40
- use function sprintf ;
41
- use function substr ;
42
10
43
11
final class ArrayFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
44
12
{
45
13
46
- private const USE_BOTH = 1 ;
47
- private const USE_KEY = 2 ;
48
- private const USE_ITEM = 3 ;
49
-
50
- public function __construct (private ReflectionProvider $ reflectionProvider )
14
+ public function __construct (private ArrayFilterFunctionReturnTypeHelper $ arrayFilterFunctionReturnTypeHelper )
51
15
{
52
16
}
53
17
@@ -62,290 +26,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
62
26
$ callbackArg = $ functionCall ->getArgs ()[1 ]->value ?? null ;
63
27
$ flagArg = $ functionCall ->getArgs ()[2 ]->value ?? null ;
64
28
65
- if ($ arrayArg === null ) {
66
- return new ArrayType (new MixedType (), new MixedType ());
67
- }
68
-
69
- $ arrayArgType = $ scope ->getType ($ arrayArg );
70
- $ arrayArgType = TypeUtils::toBenevolentUnion ($ arrayArgType );
71
- $ keyType = $ arrayArgType ->getIterableKeyType ();
72
- $ itemType = $ arrayArgType ->getIterableValueType ();
73
-
74
- if ($ itemType instanceof NeverType || $ keyType instanceof NeverType) {
75
- return new ConstantArrayType ([], []);
76
- }
77
-
78
- if ($ arrayArgType instanceof MixedType) {
79
- return new BenevolentUnionType ([
80
- new ArrayType (new MixedType (), new MixedType ()),
81
- new NullType (),
82
- ]);
83
- }
84
-
85
- if ($ callbackArg === null || $ scope ->getType ($ callbackArg )->isNull ()->yes ()) {
86
- return TypeCombinator::union (
87
- ...array_map ([$ this , 'removeFalsey ' ], $ arrayArgType ->getArrays ()),
88
- );
89
- }
90
-
91
- $ mode = $ this ->determineMode ($ flagArg , $ scope );
92
- if ($ mode === null ) {
93
- return new ArrayType ($ keyType , $ itemType );
94
- }
95
-
96
- if ($ callbackArg instanceof Closure && count ($ callbackArg ->stmts ) === 1 && count ($ callbackArg ->params ) > 0 ) {
97
- $ statement = $ callbackArg ->stmts [0 ];
98
- if ($ statement instanceof Return_ && $ statement ->expr !== null ) {
99
- if ($ mode === self ::USE_ITEM ) {
100
- $ keyVar = null ;
101
- $ itemVar = $ callbackArg ->params [0 ]->var ;
102
- } elseif ($ mode === self ::USE_KEY ) {
103
- $ keyVar = $ callbackArg ->params [0 ]->var ;
104
- $ itemVar = null ;
105
- } elseif ($ mode === self ::USE_BOTH ) {
106
- $ keyVar = $ callbackArg ->params [1 ]->var ?? null ;
107
- $ itemVar = $ callbackArg ->params [0 ]->var ;
108
- }
109
- return $ this ->filterByTruthyValue ($ scope , $ itemVar , $ arrayArgType , $ keyVar , $ statement ->expr );
110
- }
111
- } elseif ($ callbackArg instanceof ArrowFunction && count ($ callbackArg ->params ) > 0 ) {
112
- if ($ mode === self ::USE_ITEM ) {
113
- $ keyVar = null ;
114
- $ itemVar = $ callbackArg ->params [0 ]->var ;
115
- } elseif ($ mode === self ::USE_KEY ) {
116
- $ keyVar = $ callbackArg ->params [0 ]->var ;
117
- $ itemVar = null ;
118
- } elseif ($ mode === self ::USE_BOTH ) {
119
- $ keyVar = $ callbackArg ->params [1 ]->var ?? null ;
120
- $ itemVar = $ callbackArg ->params [0 ]->var ;
121
- }
122
- return $ this ->filterByTruthyValue ($ scope , $ itemVar , $ arrayArgType , $ keyVar , $ callbackArg ->expr );
123
- } elseif (
124
- ($ callbackArg instanceof FuncCall || $ callbackArg instanceof MethodCall || $ callbackArg instanceof StaticCall)
125
- && $ callbackArg ->isFirstClassCallable ()
126
- ) {
127
- [$ args , $ itemVar , $ keyVar ] = $ this ->createDummyArgs ($ mode );
128
- $ expr = clone $ callbackArg ;
129
- $ expr ->args = $ args ;
130
- return $ this ->filterByTruthyValue ($ scope , $ itemVar , $ arrayArgType , $ keyVar , $ expr );
131
- } else {
132
- $ constantStrings = $ scope ->getType ($ callbackArg )->getConstantStrings ();
133
- if (count ($ constantStrings ) > 0 ) {
134
- $ results = [];
135
- [$ args , $ itemVar , $ keyVar ] = $ this ->createDummyArgs ($ mode );
136
-
137
- foreach ($ constantStrings as $ constantString ) {
138
- $ funcName = self ::createFunctionName ($ constantString ->getValue ());
139
- if ($ funcName === null ) {
140
- $ results [] = new ErrorType ();
141
- continue ;
142
- }
143
-
144
- $ expr = new FuncCall ($ funcName , $ args );
145
- $ results [] = $ this ->filterByTruthyValue ($ scope , $ itemVar , $ arrayArgType , $ keyVar , $ expr );
146
- }
147
- return TypeCombinator::union (...$ results );
148
- }
149
- }
150
-
151
- return new ArrayType ($ keyType , $ itemType );
152
- }
153
-
154
- public function removeFalsey (Type $ type ): Type
155
- {
156
- $ falseyTypes = StaticTypeFactory::falsey ();
157
-
158
- if (count ($ type ->getConstantArrays ()) > 0 ) {
159
- $ result = [];
160
- foreach ($ type ->getConstantArrays () as $ constantArray ) {
161
- $ keys = $ constantArray ->getKeyTypes ();
162
- $ values = $ constantArray ->getValueTypes ();
163
-
164
- $ builder = ConstantArrayTypeBuilder::createEmpty ();
165
-
166
- foreach ($ values as $ offset => $ value ) {
167
- $ isFalsey = $ falseyTypes ->isSuperTypeOf ($ value );
168
-
169
- if ($ isFalsey ->maybe ()) {
170
- $ builder ->setOffsetValueType ($ keys [$ offset ], TypeCombinator::remove ($ value , $ falseyTypes ), true );
171
- } elseif ($ isFalsey ->no ()) {
172
- $ builder ->setOffsetValueType ($ keys [$ offset ], $ value , $ constantArray ->isOptionalKey ($ offset ));
173
- }
174
- }
175
-
176
- $ result [] = $ builder ->getArray ();
177
- }
178
-
179
- return TypeCombinator::union (...$ result );
180
- }
181
-
182
- $ keyType = $ type ->getIterableKeyType ();
183
- $ valueType = $ type ->getIterableValueType ();
184
-
185
- $ valueType = TypeCombinator::remove ($ valueType , $ falseyTypes );
186
-
187
- if ($ valueType instanceof NeverType) {
188
- return new ConstantArrayType ([], []);
189
- }
190
-
191
- return new ArrayType ($ keyType , $ valueType );
192
- }
193
-
194
- private function filterByTruthyValue (Scope $ scope , Error |Variable |null $ itemVar , Type $ arrayType , Error |Variable |null $ keyVar , Expr $ expr ): Type
195
- {
196
- if (!$ scope instanceof MutatingScope) {
197
- throw new ShouldNotHappenException ();
198
- }
199
-
200
- $ constantArrays = $ arrayType ->getConstantArrays ();
201
- if (count ($ constantArrays ) > 0 ) {
202
- $ results = [];
203
- foreach ($ constantArrays as $ constantArray ) {
204
- $ builder = ConstantArrayTypeBuilder::createEmpty ();
205
- $ optionalKeys = $ constantArray ->getOptionalKeys ();
206
- foreach ($ constantArray ->getKeyTypes () as $ i => $ keyType ) {
207
- $ itemType = $ constantArray ->getValueTypes ()[$ i ];
208
- [$ newKeyType , $ newItemType , $ optional ] = $ this ->processKeyAndItemType ($ scope , $ keyType , $ itemType , $ itemVar , $ keyVar , $ expr );
209
- $ optional = $ optional || in_array ($ i , $ optionalKeys , true );
210
- if ($ newKeyType instanceof NeverType || $ newItemType instanceof NeverType) {
211
- continue ;
212
- }
213
- if ($ itemType ->equals ($ newItemType ) && $ keyType ->equals ($ newKeyType )) {
214
- $ builder ->setOffsetValueType ($ keyType , $ itemType , $ optional );
215
- continue ;
216
- }
217
-
218
- $ builder ->setOffsetValueType ($ newKeyType , $ newItemType , true );
219
- }
220
-
221
- $ results [] = $ builder ->getArray ();
222
- }
223
-
224
- return TypeCombinator::union (...$ results );
225
- }
226
-
227
- [$ newKeyType , $ newItemType ] = $ this ->processKeyAndItemType ($ scope , $ arrayType ->getIterableKeyType (), $ arrayType ->getIterableValueType (), $ itemVar , $ keyVar , $ expr );
228
-
229
- if ($ newItemType instanceof NeverType || $ newKeyType instanceof NeverType) {
230
- return new ConstantArrayType ([], []);
231
- }
232
-
233
- return new ArrayType ($ newKeyType , $ newItemType );
234
- }
235
-
236
- /**
237
- * @return array{Type, Type, bool}
238
- */
239
- private function processKeyAndItemType (MutatingScope $ scope , Type $ keyType , Type $ itemType , Error |Variable |null $ itemVar , Error |Variable |null $ keyVar , Expr $ expr ): array
240
- {
241
- $ itemVarName = null ;
242
- if ($ itemVar !== null ) {
243
- if (!$ itemVar instanceof Variable || !is_string ($ itemVar ->name )) {
244
- throw new ShouldNotHappenException ();
245
- }
246
- $ itemVarName = $ itemVar ->name ;
247
- $ scope = $ scope ->assignVariable ($ itemVarName , $ itemType , new MixedType ());
248
- }
249
-
250
- $ keyVarName = null ;
251
- if ($ keyVar !== null ) {
252
- if (!$ keyVar instanceof Variable || !is_string ($ keyVar ->name )) {
253
- throw new ShouldNotHappenException ();
254
- }
255
- $ keyVarName = $ keyVar ->name ;
256
- $ scope = $ scope ->assignVariable ($ keyVarName , $ keyType , new MixedType ());
257
- }
258
-
259
- $ booleanResult = $ scope ->getType ($ expr )->toBoolean ();
260
- if ($ booleanResult ->isFalse ()->yes ()) {
261
- return [new NeverType (), new NeverType (), false ];
262
- }
263
-
264
- $ scope = $ scope ->filterByTruthyValue ($ expr );
265
-
266
- return [
267
- $ keyVarName !== null ? $ scope ->getVariableType ($ keyVarName ) : $ keyType ,
268
- $ itemVarName !== null ? $ scope ->getVariableType ($ itemVarName ) : $ itemType ,
269
- !$ booleanResult instanceof ConstantBooleanType,
270
- ];
271
- }
272
-
273
- private static function createFunctionName (string $ funcName ): ?Name
274
- {
275
- if ($ funcName === '' ) {
276
- return null ;
277
- }
278
-
279
- if ($ funcName [0 ] === '\\' ) {
280
- $ funcName = substr ($ funcName , 1 );
281
-
282
- if ($ funcName === '' ) {
283
- return null ;
284
- }
285
-
286
- return new Name \FullyQualified ($ funcName );
287
- }
288
-
289
- return new Name ($ funcName );
290
- }
291
-
292
- /**
293
- * @param self::USE_* $mode
294
- * @return array{list<Arg>, ?Variable, ?Variable}
295
- */
296
- private function createDummyArgs (int $ mode ): array
297
- {
298
- if ($ mode === self ::USE_ITEM ) {
299
- $ itemVar = new Variable ('item ' );
300
- $ keyVar = null ;
301
- $ args = [new Arg ($ itemVar )];
302
- } elseif ($ mode === self ::USE_KEY ) {
303
- $ itemVar = null ;
304
- $ keyVar = new Variable ('key ' );
305
- $ args = [new Arg ($ keyVar )];
306
- } elseif ($ mode === self ::USE_BOTH ) {
307
- $ itemVar = new Variable ('item ' );
308
- $ keyVar = new Variable ('key ' );
309
- $ args = [new Arg ($ itemVar ), new Arg ($ keyVar )];
310
- }
311
- return [$ args , $ itemVar , $ keyVar ];
312
- }
313
-
314
- /**
315
- * @param non-empty-string $constantName
316
- */
317
- private function getConstant (string $ constantName ): int
318
- {
319
- $ constant = $ this ->reflectionProvider ->getConstant (new Name ($ constantName ), null );
320
- $ valueType = $ constant ->getValueType ();
321
- if (!$ valueType instanceof ConstantIntegerType) {
322
- throw new ShouldNotHappenException (sprintf ('Constant %s does not have integer type. ' , $ constantName ));
323
- }
324
-
325
- return $ valueType ->getValue ();
326
- }
327
-
328
- /**
329
- * @return self::USE_*|null
330
- */
331
- private function determineMode (?Expr $ flagArg , Scope $ scope ): ?int
332
- {
333
- if ($ flagArg === null ) {
334
- return self ::USE_ITEM ;
335
- }
336
-
337
- $ flagValues = $ scope ->getType ($ flagArg )->getConstantScalarValues ();
338
- if (count ($ flagValues ) !== 1 ) {
339
- return null ;
340
- }
341
-
342
- if ($ flagValues [0 ] === $ this ->getConstant ('ARRAY_FILTER_USE_KEY ' )) {
343
- return self ::USE_KEY ;
344
- } elseif ($ flagValues [0 ] === $ this ->getConstant ('ARRAY_FILTER_USE_BOTH ' )) {
345
- return self ::USE_BOTH ;
346
- }
347
-
348
- return null ;
29
+ return $ this ->arrayFilterFunctionReturnTypeHelper ->getType ($ scope , $ arrayArg , $ callbackArg , $ flagArg );
349
30
}
350
31
351
32
}
0 commit comments