2525use PHPStan \Type \TypeCombinator ;
2626use PHPStan \Type \UnionType ;
2727use ValueError ;
28+ use function abs ;
2829use function count ;
30+ use function floor ;
2931use function is_array ;
32+ use function is_finite ;
33+ use function is_numeric ;
34+ use function is_string ;
35+ use function max ;
36+ use function min ;
3037use function range ;
3138
3239#[AutowiredService]
@@ -71,6 +78,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
7178 continue ;
7279 }
7380
81+ // range() would allocate every item before the length could be checked
82+ $ rangeLength = self ::getRangeLength ($ startConstant ->getValue (), $ endConstant ->getValue (), $ stepConstant ->getValue ());
83+ if ($ rangeLength !== null && $ rangeLength > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT ) {
84+ return self ::getLongRangeType ($ startConstant , $ endConstant , $ stepConstant , $ stepType );
85+ }
86+
7487 try {
7588 $ rangeValues = @range ($ startConstant ->getValue (), $ endConstant ->getValue (), $ stepConstant ->getValue ());
7689 } catch (ValueError ) {
@@ -83,35 +96,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
8396 }
8497
8598 if (count ($ rangeValues ) > self ::RANGE_LENGTH_THRESHOLD ) {
86- if (
87- $ startConstant instanceof ConstantIntegerType
88- && $ endConstant instanceof ConstantIntegerType
89- && $ stepConstant instanceof ConstantIntegerType
90- ) {
91- if ($ startConstant ->getValue () > $ endConstant ->getValue ()) {
92- $ tmp = $ startConstant ;
93- $ startConstant = $ endConstant ;
94- $ endConstant = $ tmp ;
95- }
96- return self ::getNonEmptyListOfType (
97- IntegerRangeType ::fromInterval (
98- $ startConstant ->getValue (),
99- $ endConstant ->getValue (),
100- ),
101- );
102- }
103-
104- if ($ stepType ->isFloat ()->yes ()) {
105- return self ::getNonEmptyListOfType (new FloatType ());
106- }
107-
108- return self ::getNonEmptyListOfType (
109- TypeCombinator ::union (
110- $ startConstant ->generalize (GeneralizePrecision ::moreSpecific ()),
111- $ endConstant ->generalize (GeneralizePrecision ::moreSpecific ()),
112- $ stepType ->generalize (GeneralizePrecision ::moreSpecific ()),
113- ),
114- );
99+ return self ::getLongRangeType ($ startConstant , $ endConstant , $ stepConstant , $ stepType );
115100 }
116101 $ arrayBuilder = ConstantArrayTypeBuilder ::createEmpty ();
117102 foreach ($ rangeValues as $ value ) {
@@ -162,6 +147,66 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
162147 );
163148 }
164149
150+ /**
151+ * The number of items range() creates for numeric arguments, or null when
152+ * only calling it tells - for a character range, which has at most 256
153+ * items, and for a zero, infinite or NAN argument, for which it throws.
154+ */
155+ private static function getRangeLength (int |float |string $ start , int |float |string $ end , int |float $ step ): ?float
156+ {
157+ if (is_string ($ start ) && is_string ($ end ) && !is_numeric ($ start ) && !is_numeric ($ end )) {
158+ return null ;
159+ }
160+
161+ // a non-numeric string next to a number is 0
162+ $ startNumber = is_numeric ($ start ) ? (float ) $ start : 0.0 ;
163+ $ endNumber = is_numeric ($ end ) ? (float ) $ end : 0.0 ;
164+ $ stepNumber = abs ((float ) $ step );
165+ if ($ stepNumber === 0.0 ) {
166+ return null ;
167+ }
168+
169+ $ length = abs ($ endNumber - $ startNumber ) / $ stepNumber ;
170+ if (!is_finite ($ length )) {
171+ return null ;
172+ }
173+
174+ return floor ($ length ) + 1 ;
175+ }
176+
177+ private static function getLongRangeType (
178+ ConstantIntegerType |ConstantFloatType |ConstantStringType $ startConstant ,
179+ ConstantIntegerType |ConstantFloatType |ConstantStringType $ endConstant ,
180+ ConstantIntegerType |ConstantFloatType $ stepConstant ,
181+ Type $ stepType ,
182+ ): Type
183+ {
184+ if (
185+ $ startConstant instanceof ConstantIntegerType
186+ && $ endConstant instanceof ConstantIntegerType
187+ && $ stepConstant instanceof ConstantIntegerType
188+ ) {
189+ return self ::getNonEmptyListOfType (
190+ IntegerRangeType ::fromInterval (
191+ min ($ startConstant ->getValue (), $ endConstant ->getValue ()),
192+ max ($ startConstant ->getValue (), $ endConstant ->getValue ()),
193+ ),
194+ );
195+ }
196+
197+ if ($ stepType ->isFloat ()->yes ()) {
198+ return self ::getNonEmptyListOfType (new FloatType ());
199+ }
200+
201+ return self ::getNonEmptyListOfType (
202+ TypeCombinator ::union (
203+ $ startConstant ->generalize (GeneralizePrecision ::moreSpecific ()),
204+ $ endConstant ->generalize (GeneralizePrecision ::moreSpecific ()),
205+ $ stepType ->generalize (GeneralizePrecision ::moreSpecific ()),
206+ ),
207+ );
208+ }
209+
165210 private static function getNonEmptyListOfType (Type $ type ): IntersectionType
166211 {
167212 return new IntersectionType ([
0 commit comments