Skip to content

Commit 1dfcb5b

Browse files
authored
Improve return type of range() function to always yield a non-empty-list
1 parent f26be0f commit 1dfcb5b

File tree

7 files changed

+50
-51
lines changed

7 files changed

+50
-51
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,7 +1416,6 @@ private function getKeysOrValuesArray(array $types): self
14161416
{
14171417
$count = count($types);
14181418
$autoIndexes = range($count - count($this->optionalKeys), $count);
1419-
assert($autoIndexes !== []);
14201419

14211420
if ($this->isList->yes()) {
14221421
// Optimized version for lists: Assume that if a later key exists, then earlier keys also exist.

src/Type/Php/RangeFunctionReturnTypeExtension.php

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -92,39 +92,25 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
9292
$startConstant = $endConstant;
9393
$endConstant = $tmp;
9494
}
95-
return new IntersectionType([
96-
new ArrayType(
97-
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
98-
IntegerRangeType::fromInterval($startConstant->getValue(), $endConstant->getValue()),
95+
return self::getNonEmptyListOfType(
96+
IntegerRangeType::fromInterval(
97+
$startConstant->getValue(),
98+
$endConstant->getValue(),
9999
),
100-
new NonEmptyArrayType(),
101-
new AccessoryArrayListType(),
102-
]);
100+
);
103101
}
104102

105103
if ($stepType->isFloat()->yes()) {
106-
return new IntersectionType([
107-
new ArrayType(
108-
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
109-
new FloatType(),
110-
),
111-
new NonEmptyArrayType(),
112-
new AccessoryArrayListType(),
113-
]);
104+
return self::getNonEmptyListOfType(new FloatType());
114105
}
115106

116-
return new IntersectionType([
117-
new ArrayType(
118-
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
119-
TypeCombinator::union(
120-
$startConstant->generalize(GeneralizePrecision::moreSpecific()),
121-
$endConstant->generalize(GeneralizePrecision::moreSpecific()),
122-
$stepType->generalize(GeneralizePrecision::moreSpecific()),
123-
),
107+
return self::getNonEmptyListOfType(
108+
TypeCombinator::union(
109+
$startConstant->generalize(GeneralizePrecision::moreSpecific()),
110+
$endConstant->generalize(GeneralizePrecision::moreSpecific()),
111+
$stepType->generalize(GeneralizePrecision::moreSpecific()),
124112
),
125-
new NonEmptyArrayType(),
126-
new AccessoryArrayListType(),
127-
]);
113+
);
128114
}
129115
$arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
130116
foreach ($rangeValues as $value) {
@@ -146,30 +132,45 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
146132

147133
if ($isInteger && $isStepInteger) {
148134
if ($argType instanceof IntegerRangeType) {
149-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $argType), new AccessoryArrayListType()]);
135+
return self::getNonEmptyListOfType($argType);
150136
}
151-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new IntegerType()), new AccessoryArrayListType()]);
137+
return self::getNonEmptyListOfType(new IntegerType());
152138
}
153139

154140
if ($argType->isFloat()->yes()) {
155-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new FloatType()), new AccessoryArrayListType()]);
141+
return self::getNonEmptyListOfType(new FloatType());
156142
}
157143

158144
$numberType = new UnionType([new IntegerType(), new FloatType()]);
159145
$isNumber = $numberType->isSuperTypeOf($argType)->yes();
160146
$isNumericString = $argType->isNumericString()->yes();
161147
if ($isNumber || $isNumericString) {
162-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $numberType), new AccessoryArrayListType()]);
148+
return self::getNonEmptyListOfType($numberType);
163149
}
164150

165151
if ($argType->isString()->yes()) {
166-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new StringType()), new AccessoryArrayListType()]);
152+
return self::getNonEmptyListOfType(new StringType());
167153
}
168154

169-
return new IntersectionType([new ArrayType(
170-
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
171-
new BenevolentUnionType([new IntegerType(), new FloatType(), new StringType()]),
172-
), new AccessoryArrayListType()]);
155+
return self::getNonEmptyListOfType(
156+
new BenevolentUnionType([
157+
new IntegerType(),
158+
new FloatType(),
159+
new StringType(),
160+
]),
161+
);
162+
}
163+
164+
private static function getNonEmptyListOfType(Type $type): IntersectionType
165+
{
166+
return new IntersectionType([
167+
new ArrayType(
168+
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
169+
$type,
170+
),
171+
new NonEmptyArrayType(),
172+
new AccessoryArrayListType(),
173+
]);
173174
}
174175

175176
}

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5594,19 +5594,19 @@ public static function dataRangeFunction(): array
55945594
'range(2.1, 5)',
55955595
],
55965596
[
5597-
'list<int>',
5597+
'non-empty-list<int>',
55985598
'range(2, 5, $integer)',
55995599
],
56005600
[
5601-
'list<float|int>',
5601+
'non-empty-list<float|int>',
56025602
'range($float, 5, $integer)',
56035603
],
56045604
[
5605-
'list<(float|int|string)>',
5605+
'non-empty-list<(float|int|string)>',
56065606
'range($float, $mixed, $integer)',
56075607
],
56085608
[
5609-
'list<(float|int|string)>',
5609+
'non-empty-list<(float|int|string)>',
56105610
'range($integer, $mixed)',
56115611
],
56125612
[

tests/PHPStan/Analyser/nsrt/bug-11692.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ function doFoo(int $i, float $f, $floatOrInt): void {
1212
assertType('non-empty-list<float>', range(1, 9999, .01));
1313
assertType('non-empty-list<int<1, 9999>>', range(1, 9999, 3));
1414

15-
assertType('list<float|int>', range(1, 9999, $floatOrInt));
16-
assertType('list<float|int>', range(1, 9999, $floatOrInt));
15+
assertType('non-empty-list<float|int>', range(1, 9999, $floatOrInt));
1716

18-
assertType('list<int>', range(1, 3, $i));
19-
assertType('list<float|int>', range(1, 3, $f));
17+
assertType('non-empty-list<int>', range(1, 3, $i));
18+
assertType('non-empty-list<float|int>', range(1, 3, $f));
2019

21-
assertType('list<int>', range(1, 9999, $i));
22-
assertType('list<float|int>', range(1, 9999, $f));
20+
assertType('non-empty-list<int>', range(1, 9999, $i));
21+
assertType('non-empty-list<float|int>', range(1, 9999, $f));
2322
}
2423

tests/PHPStan/Analyser/nsrt/bug-2378.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function doFoo(
1717
assertType('array{\'a\', \'b\', \'c\', \'d\'}', range('a', 'd'));
1818
assertType('array{\'a\', \'c\', \'e\', \'g\', \'i\'}', range('a', 'i', 2));
1919

20-
assertType('list<string>', range($s, $s));
20+
assertType('non-empty-list<string>', range($s, $s));
2121
}
2222

2323
}

tests/PHPStan/Analyser/nsrt/range-int-range.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function zeroToMax(
1616
int $b
1717
): void
1818
{
19-
assertType('list<int<0, max>>', range($a, $b));
19+
assertType('non-empty-list<int<0, max>>', range($a, $b));
2020
}
2121

2222
/**
@@ -28,7 +28,7 @@ public function twoToTwenty(
2828
int $b
2929
): void
3030
{
31-
assertType('list<int<2, 20>>', range($a, $b));
31+
assertType('non-empty-list<int<2, 20>>', range($a, $b));
3232
}
3333

3434
/**
@@ -40,7 +40,7 @@ public function fifteenTo5(
4040
int $b
4141
): void
4242
{
43-
assertType('list<int<5, 30>>', range($a, $b));
43+
assertType('non-empty-list<int<5, 30>>', range($a, $b));
4444
}
4545

4646
public function knownRange(

tests/PHPStan/Analyser/nsrt/range-numeric-string.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function doFoo(
1616
string $b
1717
): void
1818
{
19-
assertType('list<float|int>', range($a, $b));
19+
assertType('non-empty-list<float|int>', range($a, $b));
2020
}
2121

2222
}

0 commit comments

Comments
 (0)