Skip to content

Commit fefc531

Browse files
committed
Merge branch '1.9.x' into 1.10.x
2 parents c30ec98 + 39e645c commit fefc531

File tree

7 files changed

+134
-15
lines changed

7 files changed

+134
-15
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"hoa/compiler": "3.17.08.08",
1515
"hoa/exception": "^1.0",
1616
"hoa/regex": "1.17.01.13",
17-
"jetbrains/phpstorm-stubs": "dev-master#1a27923261cdcf36e4b7eab7efed1fb01d7305c1",
17+
"jetbrains/phpstorm-stubs": "dev-master#c1d72aec3a5fbdb3d4568076d03644c9439b78cb",
1818
"nette/bootstrap": "^3.0",
1919
"nette/di": "^3.0.11",
2020
"nette/finder": "^2.5",

composer.lock

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Type/Php/FilterFunctionReturnTypeHelper.php

+20-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PHPStan\Type\StringType;
2424
use PHPStan\Type\Type;
2525
use PHPStan\Type\TypeCombinator;
26+
use function array_key_exists;
2627
use function array_merge;
2728
use function hexdec;
2829
use function is_int;
@@ -250,31 +251,41 @@ private function applyRangeOptions(Type $type, array $typeOptions, Type $default
250251
$range = [];
251252
if (isset($typeOptions['min_range'])) {
252253
if ($typeOptions['min_range'] instanceof ConstantScalarType) {
253-
$range['min'] = $typeOptions['min_range']->getValue();
254+
$range['min'] = (int) $typeOptions['min_range']->getValue();
254255
} elseif ($typeOptions['min_range'] instanceof IntegerRangeType) {
255256
$range['min'] = $typeOptions['min_range']->getMin();
257+
} else {
258+
$range['min'] = null;
256259
}
257260
}
258261
if (isset($typeOptions['max_range'])) {
259262
if ($typeOptions['max_range'] instanceof ConstantScalarType) {
260-
$range['max'] = $typeOptions['max_range']->getValue();
263+
$range['max'] = (int) $typeOptions['max_range']->getValue();
261264
} elseif ($typeOptions['max_range'] instanceof IntegerRangeType) {
262265
$range['max'] = $typeOptions['max_range']->getMax();
266+
} else {
267+
$range['max'] = null;
263268
}
264269
}
265270

266-
if (isset($range['min']) || isset($range['max'])) {
267-
$min = isset($range['min']) && is_int($range['min']) ? $range['min'] : null;
268-
$max = isset($range['max']) && is_int($range['max']) ? $range['max'] : null;
271+
if (array_key_exists('min', $range) || array_key_exists('max', $range)) {
272+
$min = $range['min'] ?? null;
273+
$max = $range['max'] ?? null;
269274
$rangeType = IntegerRangeType::fromInterval($min, $max);
275+
$rangeTypeIsSuperType = $rangeType->isSuperTypeOf($type);
270276

271-
if (!($type instanceof ConstantScalarType)) {
272-
return $rangeType;
277+
if ($rangeTypeIsSuperType->no()) {
278+
// e.g. if 9 is filtered with a range of int<17, 19>
279+
return $defaultType;
273280
}
274281

275-
if ($rangeType->isSuperTypeOf($type)->no()) {
276-
return $defaultType;
282+
if ($rangeTypeIsSuperType->yes() && !$rangeType->equals($type)) {
283+
// e.g. if 18 or int<18, 19> are filtered with a range of int<17, 19>
284+
return $type;
277285
}
286+
287+
// Open ranges on either side means that the input is potentially not part of the range
288+
return $min === null || $max === null ? TypeCombinator::union($rangeType, $defaultType) : $rangeType;
278289
}
279290

280291
return $type;

tests/PHPStan/Analyser/data/filter-var.php

+26
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,32 @@ public function doFoo($mixed): void
1919
assertType('array<false>', filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE));
2020
}
2121

22+
/**
23+
* @param int<17, 19> $range1
24+
* @param int<1, 5> $range2
25+
* @param int<18, 19> $range3
26+
*/
27+
public function intRanges(int $int, int $min, int $max, int $range1, int $range2, int $range3): void
28+
{
29+
assertType('int<17, 19>|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => 19]]));
30+
assertType('false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => 19, 'max_range' => 17]]));
31+
assertType('0|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => null, 'max_range' => null]]));
32+
assertType('int<17, 19>|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => '17', 'max_range' => '19']]));
33+
assertType('int<min, 19>|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => 19]]));
34+
assertType('int<17, max>|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => $max]]));
35+
assertType('int<17, 19>', filter_var($range1, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => 19]]));
36+
assertType('false', filter_var(9, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => 19]]));
37+
assertType('18', filter_var(18, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => 19]]));
38+
assertType('18', filter_var(18, FILTER_VALIDATE_INT, ['options' => ['min_range' => '17', 'max_range' => '19']]));
39+
assertType('false', filter_var(-18, FILTER_VALIDATE_INT, ['options' => ['min_range' => null, 'max_range' => 19]]));
40+
assertType('false', filter_var(18, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => null]]));
41+
assertType('false', filter_var($range2, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => 19]]));
42+
assertType('int<18, 19>', filter_var($range3, FILTER_VALIDATE_INT, ['options' => ['min_range' => 17, 'max_range' => 19]]));
43+
assertType('int|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]));
44+
assertType('int|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min]]));
45+
assertType('int|false', filter_var($int, FILTER_VALIDATE_INT, ['options' => ['max_range' => $max]]));
46+
}
47+
2248
/** @param resource $resource */
2349
public function invalidInput(array $arr, object $object, $resource): void
2450
{

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -857,4 +857,16 @@ public function testLastMatchArm(bool $reportAlwaysTrueInLastCondition, array $e
857857
$this->analyse([__DIR__ . '/data/strict-comparison-last-match-arm.php'], $expectedErrors);
858858
}
859859

860+
public function testBug8776Part1(): void
861+
{
862+
$this->checkAlwaysTrueStrictComparison = true;
863+
$this->analyse([__DIR__ . '/data/bug-8776-1.php'], []);
864+
}
865+
866+
public function testBug8776Part2(): void
867+
{
868+
$this->checkAlwaysTrueStrictComparison = true;
869+
$this->analyse([__DIR__ . '/data/bug-8776-2.php'], []);
870+
}
871+
860872
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8776Part1;
4+
5+
use LogicException;
6+
7+
/**
8+
* @param mixed $value
9+
*
10+
* @return array|bool
11+
*
12+
* @throws LogicException
13+
*/
14+
function validate($value, array $schema = null)
15+
{
16+
if (is_int($value)) {
17+
if (isset($schema['minimum'])) {
18+
$minimum = $schema['minimum'];
19+
if (filter_var($minimum, FILTER_VALIDATE_INT) === false) {
20+
throw new LogicException('`minimum` must be `int`');
21+
}
22+
$options = ['options' => ['min_range' => $minimum]];
23+
$filtered = filter_var($value, FILTER_VALIDATE_INT, $options);
24+
if ($filtered === false) {
25+
return compact('minimum', 'value');
26+
}
27+
}
28+
if (isset($schema['maximum'])) {
29+
$maximum = $schema['maximum'];
30+
if (filter_var($maximum, FILTER_VALIDATE_INT) === false) {
31+
throw new LogicException('`maximum` must be `int`');
32+
}
33+
$options = ['options' => ['max_range' => $maximum]];
34+
/** @var int|false */
35+
$filtered = filter_var($value, FILTER_VALIDATE_INT, $options);
36+
if ($filtered === false) {
37+
return compact('maximum', 'value');
38+
}
39+
}
40+
// ...
41+
}
42+
if (is_string($value)) {
43+
// ...
44+
}
45+
return true;
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8776Part2;
4+
5+
class HelloWorld
6+
{
7+
public function sayHello(int $value, int $minimum): void
8+
{
9+
$options = ['options' => ['min_range' => $minimum]];
10+
$filtered = filter_var($value, FILTER_VALIDATE_INT, $options);
11+
if ($filtered === false) {
12+
return;
13+
}
14+
}
15+
16+
public function sayWorld(int $value): void
17+
{
18+
$options = ['options' => ['min_range' => 17]];
19+
$filtered = filter_var($value, FILTER_VALIDATE_INT, $options);
20+
if ($filtered === false) {
21+
return;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)