Skip to content

Commit c696481

Browse files
committed
Add lots of unit tests and combinations to cover all kinds of types.
1 parent edca7fe commit c696481

2 files changed

Lines changed: 276 additions & 17 deletions

File tree

‎src/Type/Php/FilterFunctionReturnTypeHelper.php‎

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use PHPStan\Type\MixedType;
2525
use PHPStan\Type\NeverType;
2626
use PHPStan\Type\NullType;
27+
use PHPStan\Type\ObjectShapeType;
2728
use PHPStan\Type\StringType;
2829
use PHPStan\Type\Type;
2930
use PHPStan\Type\TypeCombinator;
@@ -401,17 +402,41 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
401402
new NullType(),
402403
]);
403404

405+
$scalarOrNullType = null;
406+
$useDefaultType = false;
407+
404408
if ($scalarOrNull->isSuperTypeOf($in)->yes()) {
409+
$scalarOrNullType = $in;
410+
}
411+
412+
if ($scalarOrNull->isSuperTypeOf($in)->maybe()) {
413+
// $in is (array or object) or (scalar or null).
414+
$scalarOrNullType = TypeCombinator::remove(
415+
TypeCombinator::remove($in, new ObjectShapeType([], [])),
416+
new ArrayType(new MixedType(), new MixedType()),
417+
);
418+
// Combine future results with defaultType as $in might be an array or an object.
419+
$useDefaultType = true;
420+
}
421+
422+
if (
423+
$scalarOrNullType !== null
424+
&& ($in->isSuperTypeOf(new MixedType())->yes() || $scalarOrNull->isSuperTypeOf($scalarOrNullType)->yes())
425+
) {
405426
$canBeSanitized = $this->canStringBeSanitized($filterValue, $flagsType);
406427
if ($canBeSanitized->no()) {
407-
$stringType = $in->toString();
428+
$stringType = $scalarOrNullType->toString();
429+
} elseif ($scalarOrNullType->isString()->no()) {
430+
$stringType = $scalarOrNullType->toString();
408431
} else {
409-
$stringType = $in->isString()->no()
410-
? $in->toString()
411-
: TypeCombinator::union(TypeCombinator::remove($in, new StringType()), new StringType());
432+
$stringType = TypeCombinator::union(
433+
TypeCombinator::remove($scalarOrNullType, new StringType()),
434+
new StringType(),
435+
);
412436
}
413437

414-
return $this->handleEmptyStringNullFlag($stringType, $flagsType);
438+
$returnType = $this->handleEmptyStringNullFlag($stringType, $flagsType);
439+
return $useDefaultType ? TypeCombinator::union($defaultType, $returnType) : $returnType;
415440
}
416441
}
417442

‎tests/PHPStan/Analyser/nsrt/filter-var-returns-non-empty-string.php‎

Lines changed: 246 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Foo
1515
* @param positive-int $positive_int
1616
* @param negative-int $negative_int
1717
* @param bool $bool
18+
* @param mixed $mixed
1819
*/
1920
public function run(
2021
string $str,
@@ -25,21 +26,17 @@ public function run(
2526
int $positive_int,
2627
int $negative_int,
2728
bool $bool,
29+
mixed $mixed,
2830
): void
2931
{
32+
$array = [];
3033
$object = (object)[];
3134

3235
assertType('non-empty-string', $str);
3336

3437
$return = filter_var($str, FILTER_DEFAULT);
3538
assertType('non-empty-string', $return);
3639

37-
$return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
38-
assertType('non-empty-string', $return);
39-
40-
$return = filter_var($maybe_empty_string, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
41-
assertType('non-empty-string|null', $return);
42-
4340
$return = filter_var($object, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW);
4441
assertType('false', $return);
4542

@@ -134,9 +131,6 @@ public function run(
134131
$return = filter_var($str2, FILTER_DEFAULT);
135132
assertType("''", $return);
136133

137-
$return = filter_var('', FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
138-
assertType('null', $return);
139-
140134
$return = filter_var($str2, FILTER_VALIDATE_URL);
141135
assertType('non-falsy-string|false', $return);
142136

@@ -167,6 +161,15 @@ public function run(
167161
$return = filter_var('0x10', FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
168162
assertType('16', $return);
169163

164+
$return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
165+
assertType('non-empty-string', $return);
166+
167+
$return = filter_var($maybe_empty_string, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
168+
assertType('non-empty-string|null', $return);
169+
170+
$return = filter_var('', FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
171+
assertType('null', $return);
172+
170173
$return = filter_var(true, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
171174
assertType("'1'", $return);
172175

@@ -191,17 +194,248 @@ public function run(
191194
$return = filter_var($nullable_non_empty_string, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
192195
assertType('non-empty-string|null', $return);
193196

197+
$return = filter_var($array, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
198+
assertType('false', $return);
199+
194200
$return = filter_var($object, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
195201
assertType('false', $return);
196202

197-
$return = filter_var($this->anyOf(0.0, true), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
203+
$return = filter_var($this->anyOf($str, $maybe_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
204+
assertType('non-empty-string|null', $return);
205+
206+
$return = filter_var($this->anyOf($str, ''), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
207+
assertType('non-empty-string|null', $return);
208+
209+
$return = filter_var($this->anyOf($str, true), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
210+
assertType('non-empty-string', $return);
211+
212+
$return = filter_var($this->anyOf($str, false), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
213+
assertType('non-empty-string|null', $return);
214+
215+
$return = filter_var($this->anyOf($str, $bool), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
216+
assertType('non-empty-string|null', $return);
217+
218+
$return = filter_var($this->anyOf($str, 0.0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
219+
assertType('non-empty-string', $return);
220+
221+
$return = filter_var($this->anyOf($str, 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
222+
assertType('non-empty-string', $return);
223+
224+
$return = filter_var($this->anyOf($str, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
225+
assertType('non-empty-string|null', $return);
226+
227+
$return = filter_var($this->anyOf($str, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
228+
assertType('non-empty-string|null', $return);
229+
230+
$return = filter_var($this->anyOf($str, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
231+
assertType('non-empty-string|null', $return);
232+
233+
$return = filter_var($this->anyOf($str, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
234+
assertType('non-empty-string|false', $return);
235+
236+
$return = filter_var($this->anyOf($str, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
237+
assertType('non-empty-string|false', $return);
238+
239+
$return = filter_var($this->anyOf($maybe_empty_string, ''), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
240+
assertType('non-empty-string|null', $return);
241+
242+
$return = filter_var($this->anyOf($maybe_empty_string, true), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
243+
assertType('non-empty-string|null', $return);
244+
245+
$return = filter_var($this->anyOf($maybe_empty_string, false), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
246+
assertType('non-empty-string|null', $return);
247+
248+
$return = filter_var($this->anyOf($maybe_empty_string, $bool), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
249+
assertType('non-empty-string|null', $return);
250+
251+
$return = filter_var($this->anyOf($maybe_empty_string, 0.0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
252+
assertType('non-empty-string|null', $return);
253+
254+
$return = filter_var($this->anyOf($maybe_empty_string, 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
255+
assertType('non-empty-string|null', $return);
256+
257+
$return = filter_var($this->anyOf($maybe_empty_string, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
258+
assertType('non-empty-string|null', $return);
259+
260+
$return = filter_var($this->anyOf($maybe_empty_string, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
261+
assertType('non-empty-string|null', $return);
262+
263+
$return = filter_var($this->anyOf($maybe_empty_string, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
264+
assertType('non-empty-string|null', $return);
265+
266+
$return = filter_var($this->anyOf($maybe_empty_string, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
267+
assertType('non-empty-string|false|null', $return);
268+
269+
$return = filter_var($this->anyOf($maybe_empty_string, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
270+
assertType('non-empty-string|false|null', $return);
271+
272+
$return = filter_var($this->anyOf('', true), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
273+
assertType("'1'|null", $return);
274+
275+
$return = filter_var($this->anyOf('', false), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
276+
assertType('null', $return);
277+
278+
$return = filter_var($this->anyOf('', $bool), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
279+
assertType("'1'|null", $return);
280+
281+
$return = filter_var($this->anyOf('', 0.0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
282+
assertType("'-0'|'0'|null", $return);
283+
284+
$return = filter_var($this->anyOf('', 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
285+
assertType("'0'|null", $return);
286+
287+
$return = filter_var($this->anyOf('', null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
288+
assertType('null', $return);
289+
290+
$return = filter_var($this->anyOf('', $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
291+
assertType('non-empty-string|null', $return);
292+
293+
$return = filter_var($this->anyOf('', $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
294+
assertType('non-empty-string|null', $return);
295+
296+
$return = filter_var($this->anyOf('', $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
297+
assertType('false|null', $return);
298+
299+
$return = filter_var($this->anyOf('', $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
300+
assertType('false|null', $return);
301+
302+
$return = filter_var($this->anyOf(true, false), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
303+
assertType("'1'|null", $return);
304+
305+
$return = filter_var($this->anyOf(true, $bool), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
306+
assertType("'1'|null", $return);
307+
308+
$return = filter_var($this->anyOf(true, 0.0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
198309
assertType("'-0'|'0'|'1'", $return);
199310

200-
$return = filter_var($this->anyOf($bool, $maybe_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
201-
assertType("non-empty-string|null", $return);
311+
$return = filter_var($this->anyOf(true, 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
312+
assertType("'0'|'1'", $return);
313+
314+
$return = filter_var($this->anyOf(true, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
315+
assertType("'1'|null", $return);
316+
317+
$return = filter_var($this->anyOf(true, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
318+
assertType('non-empty-string|null', $return);
319+
320+
$return = filter_var($this->anyOf(true, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
321+
assertType('non-empty-string|null', $return);
322+
323+
$return = filter_var($this->anyOf(true, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
324+
assertType("'1'|false", $return);
325+
326+
$return = filter_var($this->anyOf(true, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
327+
assertType("'1'|false", $return);
328+
329+
$return = filter_var($this->anyOf(false, $bool), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
330+
assertType("'1'|null", $return);
331+
332+
$return = filter_var($this->anyOf(false, 0.0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
333+
assertType("'-0'|'0'|null", $return);
334+
335+
$return = filter_var($this->anyOf(false, 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
336+
assertType("'0'|null", $return);
337+
338+
$return = filter_var($this->anyOf(false, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
339+
assertType('null', $return);
340+
341+
$return = filter_var($this->anyOf(false, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
342+
assertType('non-empty-string|null', $return);
343+
344+
$return = filter_var($this->anyOf(false, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
345+
assertType('non-empty-string|null', $return);
346+
347+
$return = filter_var($this->anyOf(false, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
348+
assertType('false|null', $return);
349+
350+
$return = filter_var($this->anyOf(false, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
351+
assertType('false|null', $return);
352+
353+
$return = filter_var($this->anyOf($bool, 0.0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
354+
assertType("'-0'|'0'|'1'|null", $return);
355+
356+
$return = filter_var($this->anyOf($bool, 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
357+
assertType("'0'|'1'|null", $return);
358+
359+
$return = filter_var($this->anyOf($bool, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
360+
assertType("'1'|null", $return);
361+
362+
$return = filter_var($this->anyOf($bool, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
363+
assertType('non-empty-string|null', $return);
364+
365+
$return = filter_var($this->anyOf($bool, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
366+
assertType('non-empty-string|null', $return);
367+
368+
$return = filter_var($this->anyOf($bool, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
369+
assertType("'1'|false|null", $return);
370+
371+
$return = filter_var($this->anyOf($bool, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
372+
assertType("'1'|false|null", $return);
373+
374+
$return = filter_var($this->anyOf(0.0, 0), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
375+
assertType("'-0'|'0'", $return);
376+
377+
$return = filter_var($this->anyOf(0.0, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
378+
assertType("'-0'|'0'|null", $return);
379+
380+
$return = filter_var($this->anyOf(0.0, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
381+
assertType('non-empty-string|null', $return);
382+
383+
$return = filter_var($this->anyOf(0.0, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
384+
assertType('non-empty-string|null', $return);
385+
386+
$return = filter_var($this->anyOf(0.0, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
387+
assertType("'-0'|'0'|false", $return);
388+
389+
$return = filter_var($this->anyOf(0.0, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
390+
assertType("'-0'|'0'|false", $return);
202391

203392
$return = filter_var($this->anyOf(0, null), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
204393
assertType("'0'|null", $return);
394+
395+
$return = filter_var($this->anyOf(0, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
396+
assertType('non-empty-string|null', $return);
397+
398+
$return = filter_var($this->anyOf(0, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
399+
assertType('non-empty-string|null', $return);
400+
401+
$return = filter_var($this->anyOf(0, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
402+
assertType("'0'|false", $return);
403+
404+
$return = filter_var($this->anyOf(0, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
405+
assertType("'0'|false", $return);
406+
407+
$return = filter_var($this->anyOf(null, $nullable_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
408+
assertType('non-empty-string|null', $return);
409+
410+
$return = filter_var($this->anyOf(null, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
411+
assertType('non-empty-string|null', $return);
412+
413+
$return = filter_var($this->anyOf(null, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
414+
assertType('false|null', $return);
415+
416+
$return = filter_var($this->anyOf(null, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
417+
assertType('false|null', $return);
418+
419+
$return = filter_var($this->anyOf($nullable_string, $nullable_non_empty_string), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
420+
assertType('non-empty-string|null', $return);
421+
422+
$return = filter_var($this->anyOf($nullable_string, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
423+
assertType('non-empty-string|false|null', $return);
424+
425+
$return = filter_var($this->anyOf($nullable_string, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
426+
assertType('non-empty-string|false|null', $return);
427+
428+
$return = filter_var($this->anyOf($nullable_non_empty_string, $array), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
429+
assertType('non-empty-string|false|null', $return);
430+
431+
$return = filter_var($this->anyOf($nullable_non_empty_string, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
432+
assertType('non-empty-string|false|null', $return);
433+
434+
$return = filter_var($this->anyOf($array, $object), FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
435+
assertType('false', $return);
436+
437+
$return = filter_var($mixed, FILTER_DEFAULT, FILTER_FLAG_EMPTY_STRING_NULL);
438+
assertType('non-empty-string|false|null', $return);
205439
}
206440

207441
/**

0 commit comments

Comments
 (0)