17
17
use PHPStan \Type \Accessory \NonEmptyArrayType ;
18
18
use PHPStan \Type \ArrayType ;
19
19
use PHPStan \Type \CallableType ;
20
- use PHPStan \Type \ClosureType ;
21
20
use PHPStan \Type \Constant \ConstantStringType ;
22
21
use PHPStan \Type \FloatType ;
23
22
use PHPStan \Type \IntegerRangeType ;
@@ -104,14 +103,37 @@ private function specifyAssertAll(MethodReflection $staticMethodReflection, Stat
104
103
$ callable = $ node ->getArgs ()[0 ]->value ;
105
104
$ callableInfo = $ scope ->getType ($ callable );
106
105
107
- if (!$ callableInfo instanceof ClosureType) {
106
+ if (!$ callableInfo ->isCallable ()->yes ()) {
107
+ return new SpecifiedTypes ();
108
+ }
109
+
110
+ $ traversable = $ node ->getArgs ()[1 ]->value ;
111
+ $ traversableInfo = $ scope ->getType ($ traversable );
112
+
113
+ // If it is already not mixed (narrowed by other code, like
114
+ // '::assertAllArray()'), we could not provide any additional
115
+ // information. We can only narrow this method to 'array<mixed, mixed>'.
116
+ if (!$ traversableInfo ->equals (new MixedType ())) {
117
+ return new SpecifiedTypes ();
118
+ }
119
+
120
+ // In a negation context, we cannot precisely narrow types because we do
121
+ // not know the exact logic of the callable function. This means we
122
+ // cannot safely return 'mixed~iterable' since the value might still be
123
+ // a valid iterable.
124
+ //
125
+ // For example, a negation context with an 'is_string(...)' callable
126
+ // does not necessarily mean that the value cannot be an
127
+ // 'iterable<int>'. In such cases, it is safer to skip type narrowing
128
+ // altogether to prevent introducing new bugs into the code.
129
+ if ($ context ->false ()) {
108
130
return new SpecifiedTypes ();
109
131
}
110
132
111
133
return $ this ->typeSpecifier ->create (
112
134
$ node ->getArgs ()[1 ]->value ,
113
- new IterableType (new MixedType (true ), $ callableInfo -> getReturnType ()),
114
- TypeSpecifierContext:: createTruthy () ,
135
+ new IterableType (new MixedType (), new MixedType ()),
136
+ $ context ,
115
137
$ scope ,
116
138
);
117
139
}
@@ -123,8 +145,8 @@ private function specifyAssertAllStrings(MethodReflection $staticMethodReflectio
123
145
{
124
146
return $ this ->typeSpecifier ->create (
125
147
$ node ->getArgs ()[0 ]->value ,
126
- new IterableType (new MixedType (true ), new StringType ()),
127
- TypeSpecifierContext:: createTruthy () ,
148
+ new IterableType (new MixedType (), new StringType ()),
149
+ $ context ,
128
150
$ scope ,
129
151
);
130
152
}
@@ -136,12 +158,12 @@ private function specifyAssertAllStringable(MethodReflection $staticMethodReflec
136
158
{
137
159
// Drupal considers string as part of "stringable" as well.
138
160
$ stringable = TypeCombinator::union (new ObjectType (Stringable::class), new StringType ());
139
- $ newType = new IterableType (new MixedType (true ), $ stringable );
161
+ $ newType = new IterableType (new MixedType (), $ stringable );
140
162
141
163
return $ this ->typeSpecifier ->create (
142
164
$ node ->getArgs ()[0 ]->value ,
143
165
$ newType ,
144
- TypeSpecifierContext:: createTruthy () ,
166
+ $ context ,
145
167
$ scope ,
146
168
);
147
169
}
@@ -151,13 +173,13 @@ private function specifyAssertAllStringable(MethodReflection $staticMethodReflec
151
173
*/
152
174
private function specifyAssertAllArrays (MethodReflection $ staticMethodReflection , StaticCall $ node , Scope $ scope , TypeSpecifierContext $ context ): SpecifiedTypes
153
175
{
154
- $ arrayType = new ArrayType (new MixedType (true ), new MixedType (true ));
155
- $ newType = new IterableType (new MixedType (true ), $ arrayType );
176
+ $ arrayType = new ArrayType (new MixedType (), new MixedType ());
177
+ $ newType = new IterableType (new MixedType (), $ arrayType );
156
178
157
179
return $ this ->typeSpecifier ->create (
158
180
$ node ->getArgs ()[0 ]->value ,
159
181
$ newType ,
160
- TypeSpecifierContext:: createTruthy () ,
182
+ $ context ,
161
183
$ scope ,
162
184
);
163
185
}
@@ -171,13 +193,13 @@ private function specifyAssertStrictArray(MethodReflection $staticMethodReflecti
171
193
// In Drupal, 'strict arrays' are defined as arrays whose indexes
172
194
// consist of integers that are equal to or greater than 0.
173
195
IntegerRangeType::createAllGreaterThanOrEqualTo (0 ),
174
- new MixedType (true ),
196
+ new MixedType (),
175
197
);
176
198
177
199
return $ this ->typeSpecifier ->create (
178
200
$ node ->getArgs ()[0 ]->value ,
179
201
$ newType ,
180
- TypeSpecifierContext:: createTruthy () ,
202
+ $ context ,
181
203
$ scope ,
182
204
);
183
205
}
@@ -188,17 +210,17 @@ private function specifyAssertStrictArray(MethodReflection $staticMethodReflecti
188
210
private function specifyAssertAllStrictArrays (MethodReflection $ staticMethodReflection , StaticCall $ node , Scope $ scope , TypeSpecifierContext $ context ): SpecifiedTypes
189
211
{
190
212
$ newType = new IterableType (
191
- new MixedType (true ),
213
+ new MixedType (),
192
214
new ArrayType (
193
215
IntegerRangeType::createAllGreaterThanOrEqualTo (0 ),
194
- new MixedType (true ),
216
+ new MixedType (),
195
217
),
196
218
);
197
219
198
220
return $ this ->typeSpecifier ->create (
199
221
$ node ->getArgs ()[0 ]->value ,
200
222
$ newType ,
201
- TypeSpecifierContext:: createTruthy () ,
223
+ $ context ,
202
224
$ scope ,
203
225
);
204
226
}
@@ -229,17 +251,20 @@ private function specifyAssertAllHaveKey(MethodReflection $staticMethodReflectio
229
251
}
230
252
}
231
253
232
- $ keyTypes = [];
254
+ // @see ArrayKeyExistsFunctionTypeSpecifyingExtension.
255
+ $ possibleTypes = [
256
+ new ArrayType (new MixedType (), new MixedType ())
257
+ ];
233
258
foreach ($ keys as $ key ) {
234
- $ keyTypes [] = new HasOffsetType (new ConstantStringType ($ key ));
259
+ $ possibleTypes [] = new HasOffsetType (new ConstantStringType ($ key ));
235
260
}
236
261
237
- $ newArrayType = new ArrayType (
238
- new MixedType (true ),
239
- new ArrayType ( TypeCombinator::intersect (new MixedType (), ...$ keyTypes ), new MixedType ( true ) ),
262
+ $ newType = new IterableType (
263
+ new MixedType (),
264
+ TypeCombinator::intersect (...$ possibleTypes ),
240
265
);
241
266
242
- return $ this ->typeSpecifier ->create ($ traversableArg , $ newArrayType , TypeSpecifierContext:: createTruthy () , $ scope );
267
+ return $ this ->typeSpecifier ->create ($ traversableArg , $ newType , $ context , $ scope );
243
268
}
244
269
245
270
/**
@@ -249,8 +274,8 @@ private function specifyAssertAllIntegers(MethodReflection $staticMethodReflecti
249
274
{
250
275
return $ this ->typeSpecifier ->create (
251
276
$ node ->getArgs ()[0 ]->value ,
252
- new IterableType (new MixedType (true ), new IntegerType ()),
253
- TypeSpecifierContext:: createTruthy () ,
277
+ new IterableType (new MixedType (), new IntegerType ()),
278
+ $ context ,
254
279
$ scope ,
255
280
);
256
281
}
@@ -262,8 +287,8 @@ private function specifyAssertAllFloat(MethodReflection $staticMethodReflection,
262
287
{
263
288
return $ this ->typeSpecifier ->create (
264
289
$ node ->getArgs ()[0 ]->value ,
265
- new IterableType (new MixedType (true ), new FloatType ()),
266
- TypeSpecifierContext:: createTruthy () ,
290
+ new IterableType (new MixedType (), new FloatType ()),
291
+ $ context ,
267
292
$ scope ,
268
293
);
269
294
}
@@ -275,8 +300,8 @@ private function specifyAssertAllCallable(MethodReflection $staticMethodReflecti
275
300
{
276
301
return $ this ->typeSpecifier ->create (
277
302
$ node ->getArgs ()[0 ]->value ,
278
- new IterableType (new MixedType (true ), new CallableType ()),
279
- TypeSpecifierContext:: createTruthy () ,
303
+ new IterableType (new MixedType (), new CallableType ()),
304
+ $ context ,
280
305
$ scope ,
281
306
);
282
307
}
@@ -295,12 +320,12 @@ private function specifyAssertAllNotEmpty(MethodReflection $staticMethodReflecti
295
320
new FloatType (),
296
321
new ResourceType (),
297
322
];
298
- $ newType = new IterableType (new MixedType (true ), new UnionType ($ non_empty_types ));
323
+ $ newType = new IterableType (new MixedType (), new UnionType ($ non_empty_types ));
299
324
300
325
return $ this ->typeSpecifier ->create (
301
326
$ node ->getArgs ()[0 ]->value ,
302
327
$ newType ,
303
- TypeSpecifierContext:: createTruthy () ,
328
+ $ context ,
304
329
$ scope ,
305
330
);
306
331
}
@@ -312,8 +337,8 @@ private function specifyAssertAllNumeric(MethodReflection $staticMethodReflectio
312
337
{
313
338
return $ this ->typeSpecifier ->create (
314
339
$ node ->getArgs ()[0 ]->value ,
315
- new IterableType (new MixedType (true ), new UnionType ([new IntegerType (), new FloatType ()])),
316
- TypeSpecifierContext:: createTruthy () ,
340
+ new IterableType (new MixedType (), new UnionType ([new IntegerType (), new FloatType ()])),
341
+ $ context ,
317
342
$ scope ,
318
343
);
319
344
}
@@ -325,8 +350,8 @@ private function specifyAssertAllMatch(MethodReflection $staticMethodReflection,
325
350
{
326
351
return $ this ->typeSpecifier ->create (
327
352
$ node ->getArgs ()[1 ]->value ,
328
- new IterableType (new MixedType (true ), new StringType ()),
329
- TypeSpecifierContext:: createTruthy () ,
353
+ new IterableType (new MixedType (), new StringType ()),
354
+ $ context ,
330
355
$ scope ,
331
356
);
332
357
}
@@ -340,8 +365,8 @@ private function specifyAssertAllRegularExpressionMatch(MethodReflection $static
340
365
$ node ->getArgs ()[1 ]->value ,
341
366
// Drupal treats any non-string input in traversable as invalid
342
367
// value, so it is possible to narrow type here.
343
- new IterableType (new MixedType (true ), new StringType ()),
344
- TypeSpecifierContext:: createTruthy () ,
368
+ new IterableType (new MixedType (), new StringType ()),
369
+ $ context ,
345
370
$ scope ,
346
371
);
347
372
}
@@ -373,8 +398,8 @@ private function specifyAssertAllObjects(MethodReflection $staticMethodReflectio
373
398
374
399
return $ this ->typeSpecifier ->create (
375
400
$ node ->getArgs ()[0 ]->value ,
376
- new IterableType (new MixedType (true ), TypeCombinator::union (...$ objectTypes )),
377
- TypeSpecifierContext:: createTruthy () ,
401
+ new IterableType (new MixedType (), TypeCombinator::union (...$ objectTypes )),
402
+ $ context ,
378
403
$ scope ,
379
404
);
380
405
}
0 commit comments