Skip to content

Commit 458f5d2

Browse files
authored
support integer-range type in min/max for two arguments
1 parent 15a0ff0 commit 458f5d2

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

src/Type/Php/MinMaxFunctionReturnTypeExtension.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace PHPStan\Type\Php;
44

5+
use PhpParser\Node\Expr\BinaryOp\Smaller;
56
use PhpParser\Node\Expr\FuncCall;
7+
use PhpParser\Node\Expr\Ternary;
68
use PHPStan\Analyser\Scope;
79
use PHPStan\Reflection\FunctionReflection;
810
use PHPStan\Reflection\ParametersAcceptorSelector;
@@ -64,6 +66,31 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
6466
return new ErrorType();
6567
}
6668

69+
// rewrite min($x, $y) as $x < $y ? $x : $y
70+
// we don't handle arrays, which have different semantics
71+
$functionName = $functionReflection->getName();
72+
$args = $functionCall->args;
73+
if (count($functionCall->args) === 2) {
74+
$argType0 = $scope->getType($args[0]->value);
75+
$argType1 = $scope->getType($args[1]->value);
76+
77+
if ($argType0->isArray()->no() && $argType1->isArray()->no()) {
78+
if ($functionName === 'min') {
79+
return $scope->getType(new Ternary(
80+
new Smaller($args[0]->value, $args[1]->value),
81+
$args[0]->value,
82+
$args[1]->value
83+
));
84+
} elseif ($functionName === 'max') {
85+
return $scope->getType(new Ternary(
86+
new Smaller($args[0]->value, $args[1]->value),
87+
$args[1]->value,
88+
$args[0]->value
89+
));
90+
}
91+
}
92+
}
93+
6794
$argumentTypes = [];
6895
foreach ($functionCall->args as $arg) {
6996
$argType = $scope->getType($arg->value);
@@ -83,7 +110,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
83110
}
84111

85112
return $this->processType(
86-
$functionReflection->getName(),
113+
$functionName,
87114
$argumentTypes
88115
);
89116
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,35 @@ function dummy5(int $i, int $j): void
118118
function dummy6(string $s, string $t): void {
119119
assertType('array(?0 => non-empty-string, ?1 => non-empty-string)', array_filter([$s, $t]));
120120
}
121+
122+
class HelloWorld
123+
{
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+
}

0 commit comments

Comments
 (0)