@@ -27,6 +27,10 @@ namespace sigs = ptdecl::IntegerRangeType::sig;
2727
2828zend_class_entry *pt_ce_integer_range_type = nullptr ;
2929
30+ /* the twin's private const FLOAT_ABOVE_INT_MAX = PHP_INT_MAX + 1.0: the first
31+ * double above ZEND_LONG_MAX, exact on 64-bit (2^63) and 32-bit (2^31) builds */
32+ static constexpr double PT_IRT_FLOAT_ABOVE_INT_MAX = (double ) ZEND_LONG_MAX + 1.0 ;
33+
3034/* nothing is memoized per request since InitializerExprTypeResolver's
3135 * CALCULATE_SCALARS_LIMIT became a native constant */
3236void pt_integer_range_type_rinit ()
@@ -171,26 +175,26 @@ class IntegerRangeType
171175
172176 /* the createAll*() family: the range of integers beyond an int|float
173177 * $value. An int goes straight to fromInterval(); anything else is
174- * first held against the int limits — a float the way the VM compares
175- * int with float (both as doubles), any other value loosely — and what
176- * passes is then rounded by ceil()/floor() and (int)-cast, which is
177- * where a non-number fails under strict_types. */
178+ * held against the int limits — a float the way the VM compares int
179+ * with float (both as doubles), any other value loosely — and rounded
180+ * by ceil()/floor() and (int)-cast, which is where a non-number fails
181+ * under strict_types. Where the ceil() is held against the max instead
182+ * of $value (createAllSmallerThan(), createAllGreaterThanOrEqualTo()),
183+ * it is taken first, in the twin's order. */
178184
179185 /* fromInterval(null, $value, -1) */
180186 static zv::Val createAllSmallerThan (zval *value)
181187 {
182188 if (Z_TYPE_P (value) == IS_LONG ) return fromInterval (NullableLong::null (), NullableLong::of (Z_LVAL_P (value)), -1 );
183- int againstMax, againstMin ;
184- if (UNEXPECTED (!compareToLimits (value, againstMax, againstMin ))) return zv::Val ();
185- if (againstMax >= 0 ) { /* $value >= PHP_INT_MAX */
189+ double ceiled ;
190+ if (UNEXPECTED (!roundToDouble (value, true , ceiled ))) return zv::Val ();
191+ if (ceiled >= PT_IRT_FLOAT_ABOVE_INT_MAX ) { /* $ceil >= self::FLOAT_ABOVE_INT_MAX */
186192 return integer ();
187193 }
188- if (againstMin <= 0 ) { /* $value <= PHP_INT_MIN */
194+ if (Z_DVAL_P (value) <= ( double ) ZEND_LONG_MIN ) { /* $value <= PHP_INT_MIN */
189195 return never ();
190196 }
191- zend_long rounded;
192- if (UNEXPECTED (!roundToLong (value, true , rounded))) return zv::Val ();
193- return fromInterval (NullableLong::null (), NullableLong::of (rounded), -1 );
197+ return fromInterval (NullableLong::null (), NullableLong::of (zend_dval_to_lval (ceiled)), -1 );
194198 }
195199
196200 /* fromInterval(null, $value) */
@@ -231,17 +235,17 @@ class IntegerRangeType
231235 static zv::Val createAllGreaterThanOrEqualTo (zval *value)
232236 {
233237 if (Z_TYPE_P (value) == IS_LONG ) return fromInterval (NullableLong::of (Z_LVAL_P (value)), NullableLong::null (), 0 );
234- int againstMax, againstMin;
235- if (UNEXPECTED (!compareToLimits (value, againstMax , againstMin))) return zv::Val ();
238+ int againstMin;
239+ if (UNEXPECTED (!compareToMin (value, againstMin))) return zv::Val ();
236240 if (againstMin <= 0 ) { /* $value <= PHP_INT_MIN */
237241 return integer ();
238242 }
239- if (againstMax >= 0 ) { /* $value >= PHP_INT_MAX */
243+ double ceiled;
244+ if (UNEXPECTED (!roundToDouble (value, true , ceiled))) return zv::Val ();
245+ if (ceiled >= PT_IRT_FLOAT_ABOVE_INT_MAX ) { /* $ceil >= self::FLOAT_ABOVE_INT_MAX */
240246 return never ();
241247 }
242- zend_long rounded;
243- if (UNEXPECTED (!roundToLong (value, true , rounded))) return zv::Val ();
244- return fromInterval (NullableLong::of (rounded), NullableLong::null (), 0 );
248+ return fromInterval (NullableLong::of (zend_dval_to_lval (ceiled)), NullableLong::null (), 0 );
245249 }
246250
247251 /* $this->min / $this->max; false with an Error pending when the
@@ -1127,34 +1131,58 @@ class IntegerRangeType
11271131 * every one of the four tests false); anything else compares loosely.
11281132 * false = pending exception */
11291133 [[nodiscard]] static bool compareToLimits (zval *value, int &againstMax, int &againstMin)
1134+ {
1135+ return compareToMax (value, againstMax) && compareToMin (value, againstMin);
1136+ }
1137+
1138+ [[nodiscard]] static bool compareToMax (zval *value, int &againstMax)
11301139 {
11311140 if (EXPECTED (Z_TYPE_P (value) == IS_DOUBLE )) {
11321141 double d = Z_DVAL_P (value);
11331142 double max = (double ) ZEND_LONG_MAX ;
1134- double min = (double ) ZEND_LONG_MIN ;
11351143 againstMax = d > max ? 1 : (d < max ? -1 : (d == max ? 0 : -1 ));
1136- againstMin = d < min ? -1 : (d > min ? 1 : (d == min ? 0 : 1 ));
11371144 return true ;
11381145 }
11391146 zval limit;
11401147 ZVAL_LONG (&limit, ZEND_LONG_MAX );
11411148 againstMax = zend_compare (value, &limit);
1142- if (UNEXPECTED (EG (exception))) return false ;
1149+ return !EG (exception);
1150+ }
1151+
1152+ [[nodiscard]] static bool compareToMin (zval *value, int &againstMin)
1153+ {
1154+ if (EXPECTED (Z_TYPE_P (value) == IS_DOUBLE )) {
1155+ double d = Z_DVAL_P (value);
1156+ double min = (double ) ZEND_LONG_MIN ;
1157+ againstMin = d < min ? -1 : (d > min ? 1 : (d == min ? 0 : 1 ));
1158+ return true ;
1159+ }
1160+ zval limit;
11431161 ZVAL_LONG (&limit, ZEND_LONG_MIN );
11441162 againstMin = zend_compare (value, &limit);
11451163 return !EG (exception);
11461164 }
11471165
1148- /* (int) ceil($value) / (int) floor($value) for a non-int $value: a
1149- * float rounds and casts as the engine casts; anything else is what
1150- * ceil()/floor() reject under strict_types. false = pending exception */
1151- [[nodiscard]] static bool roundToLong (zval *value, bool useCeil, zend_long &out)
1166+ /* ceil($value) / floor($value) for a non-int $value: a float rounds;
1167+ * anything else is what ceil()/floor() reject under strict_types.
1168+ * false = pending exception */
1169+ [[nodiscard]] static bool roundToDouble (zval *value, bool useCeil, double &out)
11521170 {
11531171 if (UNEXPECTED (Z_TYPE_P (value) != IS_DOUBLE )) {
11541172 zend_type_error (" %s(): Argument #1 ($num) must be of type int|float, %s given" , useCeil ? " ceil" : " floor" , zend_zval_value_name (value));
11551173 return false ;
11561174 }
1157- out = zend_dval_to_lval (useCeil ? ceil (Z_DVAL_P (value)) : floor (Z_DVAL_P (value)));
1175+ out = useCeil ? ceil (Z_DVAL_P (value)) : floor (Z_DVAL_P (value));
1176+ return true ;
1177+ }
1178+
1179+ /* (int) ceil($value) / (int) floor($value) for a non-int $value, cast as
1180+ * the engine casts. false = pending exception */
1181+ [[nodiscard]] static bool roundToLong (zval *value, bool useCeil, zend_long &out)
1182+ {
1183+ double rounded;
1184+ if (UNEXPECTED (!roundToDouble (value, useCeil, rounded))) return false ;
1185+ out = zend_dval_to_lval (rounded);
11581186 return true ;
11591187 }
11601188
@@ -1334,13 +1362,19 @@ static void pt_irt_bound(INTERNAL_FUNCTION_PARAMETERS, bool (IntegerRangeType::*
13341362 RETURN_LONG (bound.value );
13351363}
13361364
1365+ static void pt_irt_float_above_int_max_constant (zval *out)
1366+ {
1367+ ZVAL_DOUBLE (out, PT_IRT_FLOAT_ABOVE_INT_MAX );
1368+ }
1369+
13371370PT_MINIT_REGISTRATION (pt_register_integer_range_type)
13381371{
13391372 reg::Class cls (" PHPStan\\ Type\\ IntegerRangeType" );
13401373 ptdecl::IntegerRangeType::declareClass (cls);
13411374 /* "min" and "max" must stay the first two declared properties
13421375 * (slots::min, slots::max) */
13431376 ptdecl::IntegerRangeType::declareProperties (cls);
1377+ cls.privateClassConstantValue (" FLOAT_ABOVE_INT_MAX" , pt_irt_float_above_int_max_constant);
13441378
13451379 cls.method (sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS ) {
13461380 zend_long min, max;
0 commit comments