Skip to content

Commit 4f4f737

Browse files
committed
Return false from range() for an invalid step on PHP 7
Before PHP 8.0 an invalid step was reported by a warning and a false return value instead of a ValueError.
1 parent 5b68933 commit 4f4f737

3 files changed

Lines changed: 65 additions & 14 deletions

File tree

‎src/Type/Php/RangeFunctionReturnTypeExtension.php‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\Type\ArrayType;
1515
use PHPStan\Type\BenevolentUnionType;
1616
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
17+
use PHPStan\Type\Constant\ConstantBooleanType;
1718
use PHPStan\Type\Constant\ConstantFloatType;
1819
use PHPStan\Type\Constant\ConstantIntegerType;
1920
use PHPStan\Type\Constant\ConstantStringType;
@@ -30,7 +31,6 @@
3031
use ValueError;
3132
use function abs;
3233
use function count;
33-
use function is_array;
3434
use function is_finite;
3535
use function is_float;
3636
use function is_string;
@@ -93,14 +93,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
9393
try {
9494
$rangeValues = @range($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue());
9595
} catch (ValueError) {
96-
if ($this->throwsOnAnalysedVersion($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue())) {
97-
$throwingCombinations++;
98-
}
99-
continue;
96+
$rangeValues = false;
10097
}
10198

102-
// @phpstan-ignore function.alreadyNarrowedType
103-
if (!is_array($rangeValues)) {
99+
// PHP 7 reports an invalid step by returning false instead of throwing a ValueError
100+
if ($rangeValues === false) {
101+
if ($this->failsOnAnalysedVersion($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue())) {
102+
if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
103+
$throwingCombinations++;
104+
} else {
105+
$constantReturnTypes[] = new ConstantBooleanType(false);
106+
}
107+
}
104108
continue;
105109
}
106110

@@ -193,15 +197,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
193197

194198
/**
195199
* The constant arguments are folded by calling range() on the PHP version PHPStan
196-
* itself runs on, so a ValueError only says that the runtime rejects the step.
197-
* The analysed PHP version is consulted separately before it is turned into never.
200+
* itself runs on, so a failure only says that the runtime rejects the step.
201+
* This checks whether the analysed PHP version rejects it as well.
198202
*/
199-
private function throwsOnAnalysedVersion(int|float|string $start, int|float|string $end, int|float $step): bool
203+
private function failsOnAnalysedVersion(int|float|string $start, int|float|string $end, int|float $step): bool
200204
{
201-
if (!$this->phpVersion->throwsValueErrorForInternalFunctions()) {
202-
return false;
203-
}
204-
205205
if ($this->phpVersion->hasStricterRangeFunction()) {
206206
return true;
207207
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
8+
class RangePhp74Test extends TypeInferenceTestCase
9+
{
10+
11+
public static function dataFileAsserts(): iterable
12+
{
13+
yield from self::gatherAssertTypes(__DIR__ . '/data/range-php74.php');
14+
}
15+
16+
/**
17+
* @param mixed ...$args
18+
*/
19+
#[DataProvider('dataFileAsserts')]
20+
public function testFileAsserts(
21+
string $assertType,
22+
string $file,
23+
...$args,
24+
): void
25+
{
26+
$this->assertFileAsserts($assertType, $file, ...$args);
27+
}
28+
29+
public static function getAdditionalConfigFiles(): array
30+
{
31+
return [
32+
__DIR__ . '/nodeScopeResolverPhp7.neon',
33+
];
34+
}
35+
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace RangePhp74;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
// PHP 7 reports an invalid step with a warning and returns false instead of throwing a ValueError
8+
assertType('false', range(1, 10, INF));
9+
assertType('false', range(2, 5, 0));
10+
assertType('false', range(5, 6, 3));
11+
12+
function maybeInvalidStep(bool $flag): void
13+
{
14+
assertType('array{6}|false', range($flag ? 5 : 6, 6, 3));
15+
}

0 commit comments

Comments
 (0)