Skip to content

Commit fdf732c

Browse files
authored
array_fill(): handle negative cases, support integer ranges and non-empty-array
1 parent 458f5d2 commit fdf732c

File tree

6 files changed

+69
-5
lines changed

6 files changed

+69
-5
lines changed

resources/functionMap_php80delta.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
return [
2323
'new' => [
2424
'array_combine' => ['associative-array', 'keys'=>'string[]|int[]', 'values'=>'array'],
25+
'array_fill' => ['array', 'start_key'=>'int', 'num'=>'0|positive-int', 'val'=>'mixed'],
2526
'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'],
2627
'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'],
2728
'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'],
@@ -146,6 +147,7 @@
146147
'old' => [
147148

148149
'array_combine' => ['associative-array|false', 'keys'=>'string[]|int[]', 'values'=>'array'],
150+
'array_fill' => ['array', 'start_key'=>'int', 'num'=>'int', 'val'=>'mixed'],
149151
'bcdiv' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'],
150152
'bcmod' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'],
151153
'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'],

src/File/ParentDirectoryRelativePathHelper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public function getFilenameParts(string $filename): array
5454
}
5555

5656
$dotsCount = $parentPartsCount - $i;
57+
if ($dotsCount < 0) {
58+
throw new \PHPStan\ShouldNotHappenException();
59+
}
5760

5861
return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i));
5962
}

src/Type/Php/ArrayFillFunctionReturnTypeExtension.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,33 @@
44

55
use PhpParser\Node\Expr\FuncCall;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Php\PhpVersion;
78
use PHPStan\Reflection\FunctionReflection;
89
use PHPStan\Reflection\ParametersAcceptorSelector;
910
use PHPStan\Type\Accessory\NonEmptyArrayType;
1011
use PHPStan\Type\ArrayType;
1112
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
13+
use PHPStan\Type\Constant\ConstantBooleanType;
1214
use PHPStan\Type\Constant\ConstantIntegerType;
15+
use PHPStan\Type\IntegerRangeType;
1316
use PHPStan\Type\IntegerType;
1417
use PHPStan\Type\IntersectionType;
18+
use PHPStan\Type\NeverType;
1519
use PHPStan\Type\Type;
20+
use PHPStan\Type\TypeCombinator;
1621

1722
class ArrayFillFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
1823
{
1924

2025
private const MAX_SIZE_USE_CONSTANT_ARRAY = 100;
2126

27+
private PhpVersion $phpVersion;
28+
29+
public function __construct(PhpVersion $phpVersion)
30+
{
31+
$this->phpVersion = $phpVersion;
32+
}
33+
2234
public function isFunctionSupported(FunctionReflection $functionReflection): bool
2335
{
2436
return $functionReflection->getName() === 'array_fill';
@@ -34,6 +46,23 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3446
$numberType = $scope->getType($functionCall->args[1]->value);
3547
$valueType = $scope->getType($functionCall->args[2]->value);
3648

49+
if ($numberType instanceof IntegerRangeType) {
50+
if ($numberType->getMin() < 0) {
51+
return TypeCombinator::union(
52+
new ArrayType(new IntegerType(), $valueType),
53+
new ConstantBooleanType(false)
54+
);
55+
}
56+
}
57+
58+
// check against negative-int, which is not allowed
59+
if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($numberType)->yes()) {
60+
if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
61+
return new NeverType();
62+
}
63+
return new ConstantBooleanType(false);
64+
}
65+
3766
if (
3867
$startIndexType instanceof ConstantIntegerType
3968
&& $numberType instanceof ConstantIntegerType
@@ -56,10 +85,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5685
return $arrayBuilder->getArray();
5786
}
5887

59-
if (
60-
$numberType instanceof ConstantIntegerType
61-
&& $numberType->getValue() > 0
62-
) {
88+
if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($numberType)->yes()) {
6389
return new IntersectionType([
6490
new ArrayType(new IntegerType(), $valueType),
6591
new NonEmptyArrayType(),

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5100,10 +5100,34 @@ public function dataArrayFunctions(): array
51005100
'array(1, 1, 1, 1, 1)',
51015101
'$filledIntegers',
51025102
],
5103+
[
5104+
'array()',
5105+
'$emptyFilled',
5106+
],
51035107
[
51045108
'array(1)',
51055109
'$filledIntegersWithKeys',
51065110
],
5111+
[
5112+
'array<int, \'foo\'>&nonEmpty',
5113+
'$filledNonEmptyArray',
5114+
],
5115+
[
5116+
PHP_VERSION_ID < 80000 ? 'false' : '*NEVER*',
5117+
'$filledAlwaysFalse',
5118+
],
5119+
[
5120+
PHP_VERSION_ID < 80000 ? 'false' : '*NEVER*',
5121+
'$filledNegativeConstAlwaysFalse',
5122+
],
5123+
[
5124+
'array<int, 1>|false',
5125+
'$filledByMaybeNegativeRange',
5126+
],
5127+
[
5128+
'array<int, 1>&nonEmpty',
5129+
'$filledByPositiveRange',
5130+
],
51075131
[
51085132
'array(1, 2)',
51095133
'array_keys($integerKeys)',

tests/PHPStan/Analyser/data/array-functions.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,16 @@
3737
}, 1);
3838

3939
$filledIntegers = array_fill(0, 5, 1);
40+
$emptyFilled = array_fill(3, 0, 'banana');
4041
$filledIntegersWithKeys = array_fill_keys([0], 1);
42+
/** @var negative-int $negInt */
43+
$filledAlwaysFalse = array_fill(0, $negInt, 1);
44+
/** @var positive-int $posInt */
45+
$filledNonEmptyArray = array_fill(0, $posInt, 'foo');
46+
$filledNegativeConstAlwaysFalse = array_fill(0, -5, 1);
47+
/** @var int<-3, 5> $maybeNegRange */
48+
$filledByMaybeNegativeRange = array_fill(0, $maybeNegRange, 1);
49+
$filledByPositiveRange = array_fill(0, rand(3, 5), 1);
4150

4251
$integerKeys = [
4352
1 => 'foo',

tests/PHPStan/Parallel/SchedulerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function dataSchedule(): array
7373
* @param int $maximumNumberOfProcesses
7474
* @param int $minimumNumberOfJobsPerProcess
7575
* @param int $jobSize
76-
* @param int $numberOfFiles
76+
* @param 0|positive-int $numberOfFiles
7777
* @param int $expectedNumberOfProcesses
7878
* @param array<int> $expectedJobSizes
7979
*/

0 commit comments

Comments
 (0)