Skip to content

Commit 02753c6

Browse files
staabmondrejmirtes
authored andcommitted
min() and max() do not return false on php8
1 parent 5968d9f commit 02753c6

File tree

6 files changed

+281
-53
lines changed

6 files changed

+281
-53
lines changed

src/Type/Php/MinMaxFunctionReturnTypeExtension.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpParser\Node\Expr\FuncCall;
77
use PhpParser\Node\Expr\Ternary;
88
use PHPStan\Analyser\Scope;
9+
use PHPStan\Php\PhpVersion;
910
use PHPStan\Reflection\FunctionReflection;
1011
use PHPStan\Reflection\ParametersAcceptorSelector;
1112
use PHPStan\Type\Constant\ConstantArrayType;
@@ -28,6 +29,12 @@ class MinMaxFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExte
2829
'max' => '',
2930
];
3031

32+
public function __construct(
33+
private PhpVersion $phpVersion,
34+
)
35+
{
36+
}
37+
3138
public function isFunctionSupported(FunctionReflection $functionReflection): bool
3239
{
3340
return isset($this->functionNames[$functionReflection->getName()]);
@@ -107,12 +114,12 @@ private function processArrayType(string $functionName, Type $argType): Type
107114
$resultTypes = [];
108115
foreach ($constArrayTypes as $constArrayType) {
109116
$isIterable = $constArrayType->isIterableAtLeastOnce();
110-
if ($isIterable->no()) {
117+
if ($isIterable->no() && !$this->phpVersion->throwsValueErrorForInternalFunctions()) {
111118
$resultTypes[] = new ConstantBooleanType(false);
112119
continue;
113120
}
114121
$argumentTypes = [];
115-
if (!$isIterable->yes()) {
122+
if (!$isIterable->yes() && !$this->phpVersion->throwsValueErrorForInternalFunctions()) {
116123
$argumentTypes[] = new ConstantBooleanType(false);
117124
}
118125

@@ -127,12 +134,12 @@ private function processArrayType(string $functionName, Type $argType): Type
127134
}
128135

129136
$isIterable = $argType->isIterableAtLeastOnce();
130-
if ($isIterable->no()) {
137+
if ($isIterable->no() && !$this->phpVersion->throwsValueErrorForInternalFunctions()) {
131138
return new ConstantBooleanType(false);
132139
}
133140
$iterableValueType = $argType->getIterableValueType();
134141
$argumentTypes = [];
135-
if (!$isIterable->yes()) {
142+
if (!$isIterable->yes() && !$this->phpVersion->throwsValueErrorForInternalFunctions()) {
136143
$argumentTypes[] = new ConstantBooleanType(false);
137144
}
138145

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ public function dataFileAsserts(): iterable
133133

134134
yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/staticReturnType.php');
135135

136-
yield from $this->gatherAssertTypes(__DIR__ . '/data/minmax-arrays.php');
136+
yield from $this->gatherAssertTypes(__DIR__ . '/data/minmax.php');
137+
if (PHP_VERSION_ID < 80000) {
138+
yield from $this->gatherAssertTypes(__DIR__ . '/data/minmax-arrays.php');
139+
}
140+
if (PHP_VERSION_ID >= 80000) {
141+
yield from $this->gatherAssertTypes(__DIR__ . '/data/minmax-php8.php');
142+
}
137143
yield from $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs.php');
138144
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array-key-type.php');
139145
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3133.php');
@@ -509,7 +515,12 @@ public function dataFileAsserts(): iterable
509515

510516
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5529.php');
511517

512-
yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof.php');
518+
if (PHP_VERSION_ID >= 80000) {
519+
yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof-php8.php');
520+
}
521+
if (PHP_VERSION_ID < 80000) {
522+
yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof.php');
523+
}
513524

514525
yield from $this->gatherAssertTypes(__DIR__ . '/data/div-by-zero.php');
515526

tests/PHPStan/Analyser/data/minmax-arrays.php

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -109,47 +109,8 @@ function dummy4(\DateTimeInterface $dateA, ?\DateTimeInterface $dateB): void
109109
assertType('DateTimeInterface|false', max(array_filter([$dateB])));
110110
}
111111

112-
function dummy5(int $i, int $j): void
113-
{
114-
assertType('array{0?: int<min, -1>|int<1, max>, 1?: int<min, -1>|int<1, max>}', array_filter([$i, $j]));
115-
assertType('array{1: true}', array_filter([false, true]));
116-
}
117-
118-
function dummy6(string $s, string $t): void {
119-
assertType('array{0?: non-falsy-string, 1?: non-falsy-string}', array_filter([$s, $t]));
120-
}
121-
122112
class HelloWorld
123113
{
124-
public function setRange(int $range): void
125-
{
126-
if ($range < 0) {
127-
return;
128-
}
129-
assertType('int<0, 100>', min($range, 100));
130-
assertType('int<0, 100>', min(100, $range));
131-
}
132-
133-
public function setRange2(int $range): void
134-
{
135-
if ($range > 100) {
136-
return;
137-
}
138-
assertType('int<0, 100>', max($range, 0));
139-
assertType('int<0, 100>', max(0, $range));
140-
}
141-
142-
public function boundRange(): void
143-
{
144-
/**
145-
* @var int<1, 6> $range
146-
*/
147-
$range = getFoo();
148-
149-
assertType('int<1, 4>', min($range, 4));
150-
assertType('int<4, 6>', max(4, $range));
151-
}
152-
153114
public function unionType(): void
154115
{
155116
/**
@@ -162,13 +123,5 @@ public function unionType(): void
162123

163124
assertType('0|1|2|3|4|5|6|7|8|9|false', max($numbers));
164125
assertType('9', max([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
165-
166-
/**
167-
* @var array{0, 1, 2}|array{4, 5, 6} $numbers2
168-
*/
169-
$numbers2 = getFoo();
170-
171-
assertType('0|4', min($numbers2));
172-
assertType('2|6', max($numbers2));
173126
}
174127
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
namespace MinMaxArraysPhp8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function dummy(): void
8+
{
9+
assertType('1', min([1]));
10+
assertType('*ERROR*', min([]));
11+
assertType('1', max([1]));
12+
assertType('*ERROR*', max([]));
13+
}
14+
15+
/**
16+
* @param int[] $ints
17+
*/
18+
function dummy2(array $ints): void
19+
{
20+
if (count($ints) === 0) {
21+
assertType('*ERROR*', min($ints));
22+
assertType('*ERROR*', max($ints));
23+
} else {
24+
assertType('int', min($ints));
25+
assertType('int', max($ints));
26+
}
27+
if (count($ints) === 1) {
28+
assertType('int', min($ints));
29+
assertType('int', max($ints));
30+
} else {
31+
assertType('int', min($ints));
32+
assertType('int', max($ints));
33+
}
34+
if (count($ints) !== 0) {
35+
assertType('int', min($ints));
36+
assertType('int', max($ints));
37+
} else {
38+
assertType('*ERROR*', min($ints));
39+
assertType('*ERROR*', max($ints));
40+
}
41+
if (count($ints) !== 1) {
42+
assertType('int', min($ints));
43+
assertType('int', max($ints));
44+
} else {
45+
assertType('int', min($ints));
46+
assertType('int', max($ints));
47+
}
48+
if (count($ints) > 0) {
49+
assertType('int', min($ints));
50+
assertType('int', max($ints));
51+
} else {
52+
assertType('*ERROR*', min($ints));
53+
assertType('*ERROR*', max($ints));
54+
}
55+
if (count($ints) >= 1) {
56+
assertType('int', min($ints));
57+
assertType('int', max($ints));
58+
} else {
59+
assertType('*ERROR*', min($ints));
60+
assertType('*ERROR*', max($ints));
61+
}
62+
if (count($ints) >= 2) {
63+
assertType('int', min($ints));
64+
assertType('int', max($ints));
65+
} else {
66+
assertType('int', min($ints));
67+
assertType('int', max($ints));
68+
}
69+
if (count($ints) <= 0) {
70+
assertType('*ERROR*', min($ints));
71+
assertType('*ERROR*', max($ints));
72+
} else {
73+
assertType('int', min($ints));
74+
assertType('int', max($ints));
75+
}
76+
if (count($ints) < 1) {
77+
assertType('*ERROR*', min($ints));
78+
assertType('*ERROR*', max($ints));
79+
} else {
80+
assertType('int', min($ints));
81+
assertType('int', max($ints));
82+
}
83+
if (count($ints) < 2) {
84+
assertType('int', min($ints));
85+
assertType('int', max($ints));
86+
} else {
87+
assertType('int', min($ints));
88+
assertType('int', max($ints));
89+
}
90+
}
91+
92+
/**
93+
* @param int[] $ints
94+
*/
95+
function dummy3(array $ints): void
96+
{
97+
assertType('int', min($ints));
98+
assertType('int', max($ints));
99+
}
100+
101+
102+
function dummy4(\DateTimeInterface $dateA, ?\DateTimeInterface $dateB): void
103+
{
104+
assertType('array{0: DateTimeInterface, 1?: DateTimeInterface}', array_filter([$dateA, $dateB]));
105+
assertType('DateTimeInterface', min(array_filter([$dateA, $dateB])));
106+
assertType('DateTimeInterface', max(array_filter([$dateA, $dateB])));
107+
assertType('array{0?: DateTimeInterface}', array_filter([$dateB]));
108+
assertType('DateTimeInterface', min(array_filter([$dateB])));
109+
assertType('DateTimeInterface', max(array_filter([$dateB])));
110+
}
111+
112+
113+
class HelloWorld
114+
{
115+
public function unionType(): void
116+
{
117+
/**
118+
* @var array<0|1|2|3|4|5|6|7|8|9>
119+
*/
120+
$numbers = getFoo();
121+
122+
assertType('0|1|2|3|4|5|6|7|8|9', min($numbers));
123+
assertType('0', min([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
124+
125+
assertType('0|1|2|3|4|5|6|7|8|9', max($numbers));
126+
assertType('9', max([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
127+
}
128+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace MinMax;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function dummy4(\DateTimeInterface $dateA, ?\DateTimeInterface $dateB): void
8+
{
9+
assertType('array{0: DateTimeInterface, 1?: DateTimeInterface}', array_filter([$dateA, $dateB]));
10+
assertType('DateTimeInterface', min(array_filter([$dateA, $dateB])));
11+
assertType('DateTimeInterface', max(array_filter([$dateA, $dateB])));
12+
assertType('array{0?: DateTimeInterface}', array_filter([$dateB]));
13+
}
14+
15+
function dummy5(int $i, int $j): void
16+
{
17+
assertType('array{0?: int<min, -1>|int<1, max>, 1?: int<min, -1>|int<1, max>}', array_filter([$i, $j]));
18+
assertType('array{1: true}', array_filter([false, true]));
19+
}
20+
21+
function dummy6(string $s, string $t): void {
22+
assertType('array{0?: non-falsy-string, 1?: non-falsy-string}', array_filter([$s, $t]));
23+
}
24+
25+
class HelloWorld
26+
{
27+
public function setRange(int $range): void
28+
{
29+
if ($range < 0) {
30+
return;
31+
}
32+
assertType('int<0, 100>', min($range, 100));
33+
assertType('int<0, 100>', min(100, $range));
34+
}
35+
36+
public function setRange2(int $range): void
37+
{
38+
if ($range > 100) {
39+
return;
40+
}
41+
assertType('int<0, 100>', max($range, 0));
42+
assertType('int<0, 100>', max(0, $range));
43+
}
44+
45+
public function boundRange(): void
46+
{
47+
/**
48+
* @var int<1, 6> $range
49+
*/
50+
$range = getFoo();
51+
52+
assertType('int<1, 4>', min($range, 4));
53+
assertType('int<4, 6>', max(4, $range));
54+
}
55+
56+
public function unionType(): void
57+
{
58+
/**
59+
* @var array{0, 1, 2}|array{4, 5, 6} $numbers2
60+
*/
61+
$numbers2 = getFoo();
62+
63+
assertType('0|4', min($numbers2));
64+
assertType('2|6', max($numbers2));
65+
}
66+
}

0 commit comments

Comments
 (0)