diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 4dd6249a6c1..c43e741c595 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -9,6 +9,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType; @@ -18,9 +19,7 @@ use function array_filter; use function array_map; use function assert; -use function ceil; use function count; -use function floor; use function get_class; use function is_float; use function is_int; @@ -92,17 +91,7 @@ public static function createAllSmallerThan($value): Type return self::fromInterval(null, $value, -1); } - // a float never holds PHP_INT_MAX itself, so the first float past the - // int range is (float) PHP_INT_MAX: every int is smaller than that - if ($value >= PHP_INT_MAX) { - return new IntegerType(); - } - - if ($value <= PHP_INT_MIN) { - return new NeverType(); - } - - return self::fromInterval(null, (int) ceil($value), -1); + return self::fromFloatComparison($value, '<'); } /** @@ -116,15 +105,7 @@ public static function createAllSmallerThanOrEqualTo($value): Type return self::fromInterval(null, $value); } - if ($value >= PHP_INT_MAX) { - return new IntegerType(); - } - - if ($value < PHP_INT_MIN) { - return new NeverType(); - } - - return self::fromInterval(null, (int) floor($value)); + return self::fromFloatComparison($value, '<='); } /** @@ -138,15 +119,7 @@ public static function createAllGreaterThan($value): Type return self::fromInterval($value, null, 1); } - if ($value < PHP_INT_MIN) { - return new IntegerType(); - } - - if ($value >= PHP_INT_MAX) { - return new NeverType(); - } - - return self::fromInterval((int) floor($value), null, 1); + return self::fromFloatComparison($value, '>'); } /** @@ -160,17 +133,53 @@ public static function createAllGreaterThanOrEqualTo($value): Type return self::fromInterval($value, null); } - if ($value <= PHP_INT_MIN) { - return new IntegerType(); + return self::fromFloatComparison($value, '>='); + } + + /** + * The ints for which `$int <$operator> $value` holds. PHP compares an int with a float by converting the + * int to float, which rounds ints beyond 2^53 on 64-bit builds (PHP_INT_MAX >= 2.0 ** 63 holds), so the + * bound cannot be derived from ceil() or floor() of $value. It is searched for with the comparison itself. + * + * @param string $operator '<', '<=', '>' or '>=' + */ + private static function fromFloatComparison(float $value, string $operator): Type + { + if ($operator === '<') { + $holds = static fn (int $int): bool => $int < $value; + } elseif ($operator === '<=') { + $holds = static fn (int $int): bool => $int <= $value; + } elseif ($operator === '>') { + $holds = static fn (int $int): bool => $int > $value; + } elseif ($operator === '>=') { + $holds = static fn (int $int): bool => $int >= $value; + } else { + throw new ShouldNotHappenException(); } - // (float) PHP_INT_MAX is already past the int range, so no int - // reaches it - if ($value >= PHP_INT_MAX) { - return new NeverType(); + $holdsAtMin = $holds(PHP_INT_MIN); + if ($holdsAtMin === $holds(PHP_INT_MAX)) { + // also a NaN $value, which no comparison holds for + return $holdsAtMin ? new IntegerType() : new NeverType(); + } + + // $holds() gives $holdsAtMin for $low and the opposite for $high + $low = PHP_INT_MIN; + $high = PHP_INT_MAX; + while (true) { + // floor(($low + $high) / 2) without overflowing + $middle = ($low >> 1) + ($high >> 1) + ($low & $high & 1); + if ($middle === $low) { + break; + } + if ($holds($middle) === $holdsAtMin) { + $low = $middle; + } else { + $high = $middle; + } } - return self::fromInterval((int) ceil($value), null); + return $holdsAtMin ? self::fromInterval(null, $low) : self::fromInterval($high, null); } public function getMin(): ?int diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-float-two-to-the-63.php b/tests/PHPStan/Analyser/nsrt/integer-range-float-two-to-the-63.php new file mode 100644 index 00000000000..2c172c37d64 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/integer-range-float-two-to-the-63.php @@ -0,0 +1,69 @@ +', $i); + } else { + assertType('int<9223372036854775296, max>', $i); + } + assertType('int', $i); +} + +function greaterOrEqual(int $i): void +{ + if ($i >= 9.2233720368547758E18) { + assertType('int<9223372036854775296, max>', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); +} + +function smallerOrEqual(int $i): void +{ + if ($i <= 9.2233720368547758E18) { + assertType('int', $i); + } else { + assertType('*NEVER*', $i); + } +} + +function greater(int $i): void +{ + if ($i > 9.2233720368547758E18) { + assertType('*NEVER*', $i); + } else { + assertType('int', $i); + } +} + +function intMax(): void +{ + assertType('true', 9223372036854775807 >= 9.2233720368547758E18); + assertType('false', 9223372036854775807 < 9.2233720368547758E18); + assertType('true', 9223372036854775296 >= 9.2233720368547758E18); + assertType('false', 9223372036854775295 >= 9.2233720368547758E18); +} + +// 2**53 + 3 and 2**53 + 5 both round to 2**53 + 4 + +function roundedToEven(int $i): void +{ + if ($i < 9007199254740996.0) { + assertType('int', $i); + } else { + assertType('int<9007199254740995, max>', $i); + } + if ($i <= 9007199254740996.0) { + assertType('int', $i); + } else { + assertType('int<9007199254740998, max>', $i); + } +} diff --git a/tests/PHPStan/Type/IntegerRangeTypeTest.php b/tests/PHPStan/Type/IntegerRangeTypeTest.php index 099d945b843..dc911e9d88a 100644 --- a/tests/PHPStan/Type/IntegerRangeTypeTest.php +++ b/tests/PHPStan/Type/IntegerRangeTypeTest.php @@ -3,21 +3,221 @@ namespace PHPStan\Type; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPUnit\Framework\Attributes\DataProvider; +use RuntimeException; +use function restore_error_handler; +use function set_error_handler; +use function sprintf; +use const INF; +use const NAN; use const PHP_INT_MAX; +use const PHP_INT_MIN; +use const PHP_INT_SIZE; class IntegerRangeTypeTest extends PHPStanTestCase { - public function testCreateFromFloatPastTheIntRange(): void + private const PROBED_INTS_AROUND_BOUND = 2048; + + public static function dataCreateFromFloat(): iterable + { + // PHP compares an int with a float as two floats, and the ints from 2**63 - 512 up convert to 2**63 + yield '2**63' => [ + 9.2233720368547758E18, + 'int', + 'int', + '*NEVER*', + 'int<9223372036854775296, max>', + ]; + + // the largest float smaller than 2**63, which the ints from 2**63 - 1535 to 2**63 - 513 convert to + yield '2**63 - 1024' => [ + 9.2233720368547748E18, + 'int', + 'int', + 'int<9223372036854775296, max>', + 'int<9223372036854774273, max>', + ]; + + // -2**63 is exactly PHP_INT_MIN, and the ints up to -2**63 + 512 convert to it + yield '-2**63' => [ + -9.2233720368547758E18, + '*NEVER*', + 'int', + 'int<-9223372036854775295, max>', + 'int', + ]; + + // the largest float smaller than -2**63 + yield '-2**63 - 2048' => [ + -9.2233720368547779E18, + '*NEVER*', + '*NEVER*', + 'int', + 'int', + ]; + + // the first float whose neighbours are 2 apart: 2**53 + 1 rounds down to it (to even) + yield '2**53' => [ + 9007199254740992.0, + 'int', + 'int', + 'int<9007199254740994, max>', + 'int<9007199254740992, max>', + ]; + + // 2**53 + 3 and 2**53 + 5 both round to 2**53 + 4 (to even) + yield '2**53 + 4' => [ + 9007199254740996.0, + 'int', + 'int', + 'int<9007199254740998, max>', + 'int<9007199254740995, max>', + ]; + } + + #[DataProvider('dataCreateFromFloat')] + public function testCreateFromFloat( + float $value, + string $expectedSmallerThan, + string $expectedSmallerThanOrEqualTo, + string $expectedGreaterThan, + string $expectedGreaterThanOrEqualTo, + ): void { - // PHP_INT_MAX is not representable as a float: (float) PHP_INT_MAX is - // the first float past the int range - $pastMax = (float) PHP_INT_MAX; - - $this->assertSame('int', IntegerRangeType::createAllSmallerThan($pastMax)->describe(VerbosityLevel::precise())); - $this->assertSame('int', IntegerRangeType::createAllSmallerThanOrEqualTo($pastMax)->describe(VerbosityLevel::precise())); - $this->assertSame('*NEVER*', IntegerRangeType::createAllGreaterThan($pastMax)->describe(VerbosityLevel::precise())); - $this->assertSame('*NEVER*', IntegerRangeType::createAllGreaterThanOrEqualTo($pastMax)->describe(VerbosityLevel::precise())); + if (PHP_INT_SIZE !== 8) { + $this->markTestSkipped('The bounds are those of a 64-bit int.'); + } + + $this->assertSame([ + 'createAllSmallerThan' => $expectedSmallerThan, + 'createAllSmallerThanOrEqualTo' => $expectedSmallerThanOrEqualTo, + 'createAllGreaterThan' => $expectedGreaterThan, + 'createAllGreaterThanOrEqualTo' => $expectedGreaterThanOrEqualTo, + ], [ + 'createAllSmallerThan' => IntegerRangeType::createAllSmallerThan($value)->describe(VerbosityLevel::precise()), + 'createAllSmallerThanOrEqualTo' => IntegerRangeType::createAllSmallerThanOrEqualTo($value)->describe(VerbosityLevel::precise()), + 'createAllGreaterThan' => IntegerRangeType::createAllGreaterThan($value)->describe(VerbosityLevel::precise()), + 'createAllGreaterThanOrEqualTo' => IntegerRangeType::createAllGreaterThanOrEqualTo($value)->describe(VerbosityLevel::precise()), + ]); + } + + public static function dataFloatBounds(): iterable + { + $smallerThan = static fn (float $value): Type => IntegerRangeType::createAllSmallerThan($value); + $smallerThanOrEqualTo = static fn (float $value): Type => IntegerRangeType::createAllSmallerThanOrEqualTo($value); + $greaterThan = static fn (float $value): Type => IntegerRangeType::createAllGreaterThan($value); + $greaterThanOrEqualTo = static fn (float $value): Type => IntegerRangeType::createAllGreaterThanOrEqualTo($value); + + // no int compares with NaN + yield [$smallerThan, NAN, '*NEVER*']; + yield [$smallerThanOrEqualTo, NAN, '*NEVER*']; + yield [$greaterThan, NAN, '*NEVER*']; + yield [$greaterThanOrEqualTo, NAN, '*NEVER*']; + + yield [$smallerThan, INF, 'int']; + yield [$smallerThanOrEqualTo, INF, 'int']; + yield [$greaterThan, INF, '*NEVER*']; + yield [$greaterThanOrEqualTo, INF, '*NEVER*']; + yield [$smallerThan, -INF, '*NEVER*']; + yield [$greaterThanOrEqualTo, -INF, 'int']; + + yield [$smallerThan, 2.5, 'int']; + yield [$smallerThanOrEqualTo, 2.5, 'int']; + yield [$greaterThan, 2.5, 'int<3, max>']; + yield [$greaterThanOrEqualTo, 2.5, 'int<3, max>']; + yield [$smallerThan, -2.0, 'int']; + yield [$greaterThanOrEqualTo, -2.0, 'int<-2, max>']; + } + + /** + * @param callable(float): Type $factory + */ + #[DataProvider('dataFloatBounds')] + public function testFloatBounds(callable $factory, float $value, string $expected): void + { + set_error_handler(static function (int $errno, string $errstr): bool { + throw new RuntimeException($errstr); + }); + try { + $this->assertSame($expected, $factory($value)->describe(VerbosityLevel::precise())); + } finally { + restore_error_handler(); + } + } + + public static function dataMatchesPhpComparison(): iterable + { + // floats whose int bounds depend on the int width: (float) PHP_INT_MAX is 2**63 on 64-bit + // builds and exact on 32-bit ones, and so is PHP_INT_MAX - 0.5 on 32-bit ones + $values = [ + '(float) PHP_INT_MAX' => (float) PHP_INT_MAX, + 'PHP_INT_MAX - 0.5' => PHP_INT_MAX - 0.5, + 'PHP_INT_MAX + 0.5' => PHP_INT_MAX + 0.5, + '(float) PHP_INT_MIN' => (float) PHP_INT_MIN, + 'PHP_INT_MIN + 0.5' => PHP_INT_MIN + 0.5, + 'PHP_INT_MIN - 0.5' => PHP_INT_MIN - 0.5, + '2**31' => 2147483648.0, + '2**53 + 4' => 9007199254740996.0, + '-2**53 - 4' => -9007199254740996.0, + ]; + $comparisons = [ + '<' => [ + static fn (float $value): Type => IntegerRangeType::createAllSmallerThan($value), + static fn (int $int, float $value): bool => $int < $value, + ], + '<=' => [ + static fn (float $value): Type => IntegerRangeType::createAllSmallerThanOrEqualTo($value), + static fn (int $int, float $value): bool => $int <= $value, + ], + '>' => [ + static fn (float $value): Type => IntegerRangeType::createAllGreaterThan($value), + static fn (int $int, float $value): bool => $int > $value, + ], + '>=' => [ + static fn (float $value): Type => IntegerRangeType::createAllGreaterThanOrEqualTo($value), + static fn (int $int, float $value): bool => $int >= $value, + ], + ]; + foreach ($values as $label => $value) { + foreach ($comparisons as $operator => [$factory, $compare]) { + yield sprintf('$int %s %s', $operator, $label) => [$factory, $compare, $value]; + } + } + } + + /** + * @param callable(float): Type $factory + * @param callable(int, float): bool $compare + */ + #[DataProvider('dataMatchesPhpComparison')] + public function testMatchesPhpComparison(callable $factory, callable $compare, float $value): void + { + $type = $factory($value); + + // a comparison with a float flips at most once, near the float or at an end of the int range; + // the ints converting to one float near 2**63 span 1024 of them + $centers = [PHP_INT_MIN, PHP_INT_MAX]; + if ($value > PHP_INT_MIN && $value < PHP_INT_MAX) { + $centers[] = (int) $value; + } + $mismatches = []; + foreach ($centers as $center) { + // clamped to the int range without overflowing into float + $from = $center < PHP_INT_MIN + self::PROBED_INTS_AROUND_BOUND ? PHP_INT_MIN : $center - self::PROBED_INTS_AROUND_BOUND; + $to = $center > PHP_INT_MAX - self::PROBED_INTS_AROUND_BOUND ? PHP_INT_MAX : $center + self::PROBED_INTS_AROUND_BOUND; + for ($int = $from;; $int++) { + if ($type->isSuperTypeOf(new ConstantIntegerType($int))->yes() !== $compare($int, $value)) { + $mismatches[] = $int; + } + if ($int === $to) { + break; + } + } + } + + $this->assertSame([], $mismatches, $type->describe(VerbosityLevel::precise())); } } diff --git a/turbo-ext/src/IntegerRangeType.cpp b/turbo-ext/src/IntegerRangeType.cpp index 1afa62f7565..f0be460d0e6 100644 --- a/turbo-ext/src/IntegerRangeType.cpp +++ b/turbo-ext/src/IntegerRangeType.cpp @@ -23,8 +23,6 @@ namespace slots = ptdecl::IntegerRangeType::slot; namespace sigs = ptdecl::IntegerRangeType::sig; -#include - zend_class_entry *pt_ce_integer_range_type = nullptr; /* nothing is memoized per request since InitializerExprTypeResolver's @@ -170,78 +168,73 @@ class IntegerRangeType } /* the createAll*() family: the range of integers beyond an int|float - * $value. An int goes straight to fromInterval(); anything else is - * first held against the int limits — a float the way the VM compares - * int with float (both as doubles), any other value loosely — and what - * passes is then rounded by ceil()/floor() and (int)-cast, which is - * where a non-number fails under strict_types. */ + * $value. An int goes straight to fromInterval(); anything else goes to + * fromFloatComparison(), whose float parameter is where a non-float fails + * under strict_types. */ /* fromInterval(null, $value, -1) */ static zv::Val createAllSmallerThan(zval *value) { if (Z_TYPE_P(value) == IS_LONG) return fromInterval(NullableLong::null(), NullableLong::of(Z_LVAL_P(value)), -1); - int againstMax, againstMin; - if (UNEXPECTED(!compareToLimits(value, againstMax, againstMin))) return zv::Val(); - if (againstMax >= 0) { /* $value >= PHP_INT_MAX */ - return integer(); - } - if (againstMin <= 0) { /* $value <= PHP_INT_MIN */ - return never(); - } - zend_long rounded; - if (UNEXPECTED(!roundToLong(value, true, rounded))) return zv::Val(); - return fromInterval(NullableLong::null(), NullableLong::of(rounded), -1); + return fromFloatComparison(value, [](zend_long i, double f) { return (double) i < f; }); /* fromFloatComparison($value, '<') */ } /* fromInterval(null, $value) */ static zv::Val createAllSmallerThanOrEqualTo(zval *value) { if (Z_TYPE_P(value) == IS_LONG) return fromInterval(NullableLong::null(), NullableLong::of(Z_LVAL_P(value)), 0); - int againstMax, againstMin; - if (UNEXPECTED(!compareToLimits(value, againstMax, againstMin))) return zv::Val(); - if (againstMax >= 0) { /* $value >= PHP_INT_MAX */ - return integer(); - } - if (againstMin < 0) { /* $value < PHP_INT_MIN */ - return never(); - } - zend_long rounded; - if (UNEXPECTED(!roundToLong(value, false, rounded))) return zv::Val(); - return fromInterval(NullableLong::null(), NullableLong::of(rounded), 0); + return fromFloatComparison(value, [](zend_long i, double f) { return (double) i <= f; }); /* fromFloatComparison($value, '<=') */ } /* fromInterval($value, null, 1) */ static zv::Val createAllGreaterThan(zval *value) { if (Z_TYPE_P(value) == IS_LONG) return fromInterval(NullableLong::of(Z_LVAL_P(value)), NullableLong::null(), 1); - int againstMax, againstMin; - if (UNEXPECTED(!compareToLimits(value, againstMax, againstMin))) return zv::Val(); - if (againstMin < 0) { /* $value < PHP_INT_MIN */ - return integer(); - } - if (againstMax >= 0) { /* $value >= PHP_INT_MAX */ - return never(); - } - zend_long rounded; - if (UNEXPECTED(!roundToLong(value, false, rounded))) return zv::Val(); - return fromInterval(NullableLong::of(rounded), NullableLong::null(), 1); + return fromFloatComparison(value, [](zend_long i, double f) { return (double) i > f; }); /* fromFloatComparison($value, '>') */ } /* fromInterval($value, null) */ static zv::Val createAllGreaterThanOrEqualTo(zval *value) { if (Z_TYPE_P(value) == IS_LONG) return fromInterval(NullableLong::of(Z_LVAL_P(value)), NullableLong::null(), 0); - int againstMax, againstMin; - if (UNEXPECTED(!compareToLimits(value, againstMax, againstMin))) return zv::Val(); - if (againstMin <= 0) { /* $value <= PHP_INT_MIN */ - return integer(); + return fromFloatComparison(value, [](zend_long i, double f) { return (double) i >= f; }); /* fromFloatComparison($value, '>=') */ + } + + /* fromFloatComparison($value, $operator): the ints for which + * `$int <$operator> $value` holds. holds() is the VM's int-with-float + * comparison, both sides as doubles, which rounds ints beyond 2^53 on + * 64-bit builds; the bound is bisected with it, in the twin's order. */ + template + static zv::Val fromFloatComparison(zval *value, Holds holds) + { + if (UNEXPECTED(Z_TYPE_P(value) != IS_DOUBLE)) { + zend_type_error("PHPStan\\Type\\IntegerRangeType::fromFloatComparison(): Argument #1 ($value) must be of type float, %s given", zend_zval_value_name(value)); + return zv::Val(); } - if (againstMax >= 0) { /* $value >= PHP_INT_MAX */ - return never(); + double f = Z_DVAL_P(value); + bool holdsAtMin = holds(ZEND_LONG_MIN, f); + if (holdsAtMin == holds(ZEND_LONG_MAX, f)) { + /* also a NAN $value, which no comparison holds for */ + return holdsAtMin ? integer() : never(); } - zend_long rounded; - if (UNEXPECTED(!roundToLong(value, true, rounded))) return zv::Val(); - return fromInterval(NullableLong::of(rounded), NullableLong::null(), 0); + + /* holds() gives holdsAtMin for low and the opposite for high */ + zend_long low = ZEND_LONG_MIN; + zend_long high = ZEND_LONG_MAX; + while (true) { + /* floor((low + high) / 2) without overflowing */ + zend_long middle = (low >> 1) + (high >> 1) + (low & high & 1); + if (middle == low) break; + if (holds(middle, f) == holdsAtMin) { + low = middle; + } else { + high = middle; + } + } + + return holdsAtMin + ? fromInterval(NullableLong::null(), NullableLong::of(low), 0) + : fromInterval(NullableLong::of(high), NullableLong::null(), 0); } /* $this->min / $this->max; false with an Error pending when the @@ -1119,45 +1112,6 @@ class IntegerRangeType return pt_type_call(Z_OBJ_P(result.raw()), PT_LC("toacceptsresult"), 0, NULL); } - /* the three-way comparisons of a non-int $value against PHP_INT_MAX and - * PHP_INT_MIN — the callers read `>`, `>=`, `<`, `<=` off the sign. A - * float compares as the VM compares int with float, both sides as - * doubles — so NAN is neither above nor below either limit, on either - * side (a NAN answer of -1 against the max and 1 against the min keeps - * every one of the four tests false); anything else compares loosely. - * false = pending exception */ - [[nodiscard]] static bool compareToLimits(zval *value, int &againstMax, int &againstMin) - { - if (EXPECTED(Z_TYPE_P(value) == IS_DOUBLE)) { - double d = Z_DVAL_P(value); - double max = (double) ZEND_LONG_MAX; - double min = (double) ZEND_LONG_MIN; - againstMax = d > max ? 1 : (d < max ? -1 : (d == max ? 0 : -1)); - againstMin = d < min ? -1 : (d > min ? 1 : (d == min ? 0 : 1)); - return true; - } - zval limit; - ZVAL_LONG(&limit, ZEND_LONG_MAX); - againstMax = zend_compare(value, &limit); - if (UNEXPECTED(EG(exception))) return false; - ZVAL_LONG(&limit, ZEND_LONG_MIN); - againstMin = zend_compare(value, &limit); - return !EG(exception); - } - - /* (int) ceil($value) / (int) floor($value) for a non-int $value: a - * float rounds and casts as the engine casts; anything else is what - * ceil()/floor() reject under strict_types. false = pending exception */ - [[nodiscard]] static bool roundToLong(zval *value, bool useCeil, zend_long &out) - { - if (UNEXPECTED(Z_TYPE_P(value) != IS_DOUBLE)) { - zend_type_error("%s(): Argument #1 ($num) must be of type int|float, %s given", useCeil ? "ceil" : "floor", zend_zval_value_name(value)); - return false; - } - out = zend_dval_to_lval(useCeil ? ceil(Z_DVAL_P(value)) : floor(Z_DVAL_P(value))); - return true; - } - /* $base ** $exponent — int, or float on overflow / negative exponent; * false = pending exception */ [[nodiscard]] static bool phpPow(zend_long base, zend_long exponent, zval *out) @@ -1401,6 +1355,34 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) pt_irt_create_all(INTERNAL_FUNCTION_PARAM_PASSTHRU, IntegerRangeType::createAllGreaterThanOrEqualTo); }); + /* only reflection reaches the private twin: the factories above bisect + * natively. An unknown $operator fails at the first comparison, where the + * twin's match() reaches its default arm */ + cls.method(sigs::fromFloatComparison, [](INTERNAL_FUNCTION_PARAMETERS) { + double value; + zend_string *op; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_DOUBLE(value) + Z_PARAM_STR(op) + ZEND_PARSE_PARAMETERS_END(); + zval boxed; + ZVAL_DOUBLE(&boxed, value); + if (zend_string_equals_literal(op, "<")) { + PT_RETURN_VAL(IntegerRangeType::fromFloatComparison(&boxed, [](zend_long i, double f) { return (double) i < f; })); + } + if (zend_string_equals_literal(op, "<=")) { + PT_RETURN_VAL(IntegerRangeType::fromFloatComparison(&boxed, [](zend_long i, double f) { return (double) i <= f; })); + } + if (zend_string_equals_literal(op, ">")) { + PT_RETURN_VAL(IntegerRangeType::fromFloatComparison(&boxed, [](zend_long i, double f) { return (double) i > f; })); + } + if (zend_string_equals_literal(op, ">=")) { + PT_RETURN_VAL(IntegerRangeType::fromFloatComparison(&boxed, [](zend_long i, double f) { return (double) i >= f; })); + } + pt_throw_should_not_happen(); + RETURN_THROWS(); + }); + cls.method(sigs::getMin, [](INTERNAL_FUNCTION_PARAMETERS) { pt_irt_bound(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntegerRangeType::getMin); }); diff --git a/turbo-ext/src/generated/IntegerRangeType.h b/turbo-ext/src/generated/IntegerRangeType.h index a2d7db5f18b..108bfbb05e6 100644 --- a/turbo-ext/src/generated/IntegerRangeType.h +++ b/turbo-ext/src/generated/IntegerRangeType.h @@ -50,54 +50,56 @@ inline constexpr char strings[] = "createAllSmallerThanOrEqualTo\0" /* 142 */ "createAllGreaterThan\0" /* 172 */ "createAllGreaterThanOrEqualTo\0" /* 193 */ - "getMin\0" /* 223 */ - "getMax\0" /* 230 */ - "level\0" /* 237 */ - "PHPStan\\Type\\VerbosityLevel\0" /* 243 */ - "describe\0" /* 271 */ - "amount\0" /* 280 */ - "type\0" /* 287 */ - "strictTypes\0" /* 292 */ - "PHPStan\\Type\\AcceptsResult\0" /* 304 */ - "accepts\0" /* 331 */ - "PHPStan\\Type\\IsSuperTypeOfResult\0" /* 339 */ - "isSuperTypeOf\0" /* 372 */ - "otherType\0" /* 386 */ - "isSubTypeOf\0" /* 396 */ - "PHPStan\\Type\\UnionType\0" /* 408 */ - "isSubTypeOfUnionWithReason\0" /* 431 */ - "acceptingType\0" /* 458 */ - "isAcceptedBy\0" /* 472 */ - "equals\0" /* 485 */ - "precision\0" /* 492 */ - "PHPStan\\Type\\GeneralizePrecision\0" /* 502 */ - "generalize\0" /* 535 */ - "phpVersion\0" /* 546 */ - "PHPStan\\Php\\PhpVersion\0" /* 557 */ - "PHPStan\\TrinaryLogic\0" /* 580 */ - "isSmallerThan\0" /* 601 */ - "isSmallerThanOrEqual\0" /* 615 */ - "isGreaterThan\0" /* 636 */ - "isGreaterThanOrEqual\0" /* 650 */ - "getSmallerType\0" /* 671 */ - "getSmallerOrEqualType\0" /* 686 */ - "getGreaterType\0" /* 708 */ - "getGreaterOrEqualType\0" /* 723 */ - "PHPStan\\Type\\BooleanType\0" /* 745 */ - "toBoolean\0" /* 770 */ - "toAbsoluteNumber\0" /* 780 */ - "toBitwiseNotType\0" /* 797 */ - "toString\0" /* 814 */ - "tryUnion\0" /* 823 */ - "tryIntersect\0" /* 832 */ - "typeToRemove\0" /* 845 */ - "tryRemove\0" /* 858 */ - "exponent\0" /* 868 */ - "exponentiate\0" /* 877 */ - "getFiniteTypes\0" /* 890 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 905 */ - "toPhpDocNode\0" /* 944 */ - "looseCompare"; /* 957 */ + "operator\0" /* 223 */ + "fromFloatComparison\0" /* 232 */ + "getMin\0" /* 252 */ + "getMax\0" /* 259 */ + "level\0" /* 266 */ + "PHPStan\\Type\\VerbosityLevel\0" /* 272 */ + "describe\0" /* 300 */ + "amount\0" /* 309 */ + "type\0" /* 316 */ + "strictTypes\0" /* 321 */ + "PHPStan\\Type\\AcceptsResult\0" /* 333 */ + "accepts\0" /* 360 */ + "PHPStan\\Type\\IsSuperTypeOfResult\0" /* 368 */ + "isSuperTypeOf\0" /* 401 */ + "otherType\0" /* 415 */ + "isSubTypeOf\0" /* 425 */ + "PHPStan\\Type\\UnionType\0" /* 437 */ + "isSubTypeOfUnionWithReason\0" /* 460 */ + "acceptingType\0" /* 487 */ + "isAcceptedBy\0" /* 501 */ + "equals\0" /* 514 */ + "precision\0" /* 521 */ + "PHPStan\\Type\\GeneralizePrecision\0" /* 531 */ + "generalize\0" /* 564 */ + "phpVersion\0" /* 575 */ + "PHPStan\\Php\\PhpVersion\0" /* 586 */ + "PHPStan\\TrinaryLogic\0" /* 609 */ + "isSmallerThan\0" /* 630 */ + "isSmallerThanOrEqual\0" /* 644 */ + "isGreaterThan\0" /* 665 */ + "isGreaterThanOrEqual\0" /* 679 */ + "getSmallerType\0" /* 700 */ + "getSmallerOrEqualType\0" /* 715 */ + "getGreaterType\0" /* 737 */ + "getGreaterOrEqualType\0" /* 752 */ + "PHPStan\\Type\\BooleanType\0" /* 774 */ + "toBoolean\0" /* 799 */ + "toAbsoluteNumber\0" /* 809 */ + "toBitwiseNotType\0" /* 826 */ + "toString\0" /* 843 */ + "tryUnion\0" /* 852 */ + "tryIntersect\0" /* 861 */ + "typeToRemove\0" /* 874 */ + "tryRemove\0" /* 887 */ + "exponent\0" /* 897 */ + "exponentiate\0" /* 906 */ + "getFiniteTypes\0" /* 919 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 934 */ + "toPhpDocNode\0" /* 973 */ + "looseCompare"; /* 986 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_NULL | MAY_BE_LONG), /* __construct $min */ reg::packed(4, MAY_BE_NULL | MAY_BE_LONG), /* __construct $max */ @@ -119,65 +121,68 @@ inline constexpr reg::PackedArg args[] = { reg::packed(28, 0, 29), /* createAllGreaterThan return */ reg::packed(115, 0), /* createAllGreaterThanOrEqualTo $value */ reg::packed(28, 0, 29), /* createAllGreaterThanOrEqualTo return */ + reg::packed(115, MAY_BE_DOUBLE), /* fromFloatComparison $value */ + reg::packed(223, MAY_BE_STRING), /* fromFloatComparison $operator */ + reg::packed(28, 0, 29), /* fromFloatComparison return */ reg::packed(28, MAY_BE_NULL | MAY_BE_LONG), /* getMin return */ reg::packed(28, MAY_BE_NULL | MAY_BE_LONG), /* getMax return */ - reg::packed(237, 0, 243), /* describe $level */ + reg::packed(266, 0, 272), /* describe $level */ reg::packed(28, MAY_BE_STRING), /* describe return */ - reg::packed(280, MAY_BE_LONG), /* shift $amount */ + reg::packed(309, MAY_BE_LONG), /* shift $amount */ reg::packed(28, 0, 29), /* shift return */ - reg::packed(287, 0, 29), /* accepts $type */ - reg::packed(292, MAY_BE_BOOL), /* accepts $strictTypes */ - reg::packed(28, 0, 304), /* accepts return */ - reg::packed(287, 0, 29), /* isSuperTypeOf $type */ - reg::packed(28, 0, 339), /* isSuperTypeOf return */ - reg::packed(386, 0, 29), /* isSubTypeOf $otherType */ - reg::packed(28, 0, 339), /* isSubTypeOf return */ - reg::packed(386, 0, 408), /* isSubTypeOfUnionWithReason $otherType */ - reg::packed(28, 0, 339), /* isSubTypeOfUnionWithReason return */ - reg::packed(458, 0, 29), /* isAcceptedBy $acceptingType */ - reg::packed(292, MAY_BE_BOOL), /* isAcceptedBy $strictTypes */ - reg::packed(28, 0, 304), /* isAcceptedBy return */ - reg::packed(287, 0, 29), /* equals $type */ + reg::packed(316, 0, 29), /* accepts $type */ + reg::packed(321, MAY_BE_BOOL), /* accepts $strictTypes */ + reg::packed(28, 0, 333), /* accepts return */ + reg::packed(316, 0, 29), /* isSuperTypeOf $type */ + reg::packed(28, 0, 368), /* isSuperTypeOf return */ + reg::packed(415, 0, 29), /* isSubTypeOf $otherType */ + reg::packed(28, 0, 368), /* isSubTypeOf return */ + reg::packed(415, 0, 437), /* isSubTypeOfUnionWithReason $otherType */ + reg::packed(28, 0, 368), /* isSubTypeOfUnionWithReason return */ + reg::packed(487, 0, 29), /* isAcceptedBy $acceptingType */ + reg::packed(321, MAY_BE_BOOL), /* isAcceptedBy $strictTypes */ + reg::packed(28, 0, 333), /* isAcceptedBy return */ + reg::packed(316, 0, 29), /* equals $type */ reg::packed(28, MAY_BE_BOOL), /* equals return */ - reg::packed(492, 0, 502), /* generalize $precision */ + reg::packed(521, 0, 531), /* generalize $precision */ reg::packed(28, 0, 29), /* generalize return */ - reg::packed(386, 0, 29), /* isSmallerThan $otherType */ - reg::packed(546, 0, 557), /* isSmallerThan $phpVersion */ - reg::packed(28, 0, 580), /* isSmallerThan return */ - reg::packed(386, 0, 29), /* isSmallerThanOrEqual $otherType */ - reg::packed(546, 0, 557), /* isSmallerThanOrEqual $phpVersion */ - reg::packed(28, 0, 580), /* isSmallerThanOrEqual return */ - reg::packed(386, 0, 29), /* isGreaterThan $otherType */ - reg::packed(546, 0, 557), /* isGreaterThan $phpVersion */ - reg::packed(28, 0, 580), /* isGreaterThan return */ - reg::packed(386, 0, 29), /* isGreaterThanOrEqual $otherType */ - reg::packed(546, 0, 557), /* isGreaterThanOrEqual $phpVersion */ - reg::packed(28, 0, 580), /* isGreaterThanOrEqual return */ - reg::packed(546, 0, 557), /* getSmallerType $phpVersion */ + reg::packed(415, 0, 29), /* isSmallerThan $otherType */ + reg::packed(575, 0, 586), /* isSmallerThan $phpVersion */ + reg::packed(28, 0, 609), /* isSmallerThan return */ + reg::packed(415, 0, 29), /* isSmallerThanOrEqual $otherType */ + reg::packed(575, 0, 586), /* isSmallerThanOrEqual $phpVersion */ + reg::packed(28, 0, 609), /* isSmallerThanOrEqual return */ + reg::packed(415, 0, 29), /* isGreaterThan $otherType */ + reg::packed(575, 0, 586), /* isGreaterThan $phpVersion */ + reg::packed(28, 0, 609), /* isGreaterThan return */ + reg::packed(415, 0, 29), /* isGreaterThanOrEqual $otherType */ + reg::packed(575, 0, 586), /* isGreaterThanOrEqual $phpVersion */ + reg::packed(28, 0, 609), /* isGreaterThanOrEqual return */ + reg::packed(575, 0, 586), /* getSmallerType $phpVersion */ reg::packed(28, 0, 29), /* getSmallerType return */ - reg::packed(546, 0, 557), /* getSmallerOrEqualType $phpVersion */ + reg::packed(575, 0, 586), /* getSmallerOrEqualType $phpVersion */ reg::packed(28, 0, 29), /* getSmallerOrEqualType return */ - reg::packed(546, 0, 557), /* getGreaterType $phpVersion */ + reg::packed(575, 0, 586), /* getGreaterType $phpVersion */ reg::packed(28, 0, 29), /* getGreaterType return */ - reg::packed(546, 0, 557), /* getGreaterOrEqualType $phpVersion */ + reg::packed(575, 0, 586), /* getGreaterOrEqualType $phpVersion */ reg::packed(28, 0, 29), /* getGreaterOrEqualType return */ - reg::packed(28, 0, 745), /* toBoolean return */ + reg::packed(28, 0, 774), /* toBoolean return */ reg::packed(28, 0, 29), /* toAbsoluteNumber return */ reg::packed(28, 0, 29), /* toBitwiseNotType return */ reg::packed(28, 0, 29), /* toString return */ - reg::packed(386, 0, 29), /* tryUnion $otherType */ + reg::packed(415, 0, 29), /* tryUnion $otherType */ reg::packed(28, MAY_BE_NULL, 29), /* tryUnion return */ - reg::packed(386, 0, 29), /* tryIntersect $otherType */ + reg::packed(415, 0, 29), /* tryIntersect $otherType */ reg::packed(28, MAY_BE_NULL, 29), /* tryIntersect return */ - reg::packed(845, 0, 29), /* tryRemove $typeToRemove */ + reg::packed(874, 0, 29), /* tryRemove $typeToRemove */ reg::packed(28, MAY_BE_NULL, 29), /* tryRemove return */ - reg::packed(868, 0, 29), /* exponentiate $exponent */ + reg::packed(897, 0, 29), /* exponentiate $exponent */ reg::packed(28, 0, 29), /* exponentiate return */ reg::packed(28, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(28, 0, 905), /* toPhpDocNode return */ - reg::packed(287, 0, 29), /* looseCompare $type */ - reg::packed(546, 0, 557), /* looseCompare $phpVersion */ - reg::packed(28, 0, 745), /* looseCompare return */ + reg::packed(28, 0, 934), /* toPhpDocNode return */ + reg::packed(316, 0, 29), /* looseCompare $type */ + reg::packed(575, 0, 586), /* looseCompare $phpVersion */ + reg::packed(28, 0, 774), /* looseCompare return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -191,36 +196,37 @@ inline constexpr sigtab::Sig createAllSmallerThan = { { 121 /* createAllSmallerT inline constexpr sigtab::Sig createAllSmallerThanOrEqualTo = { { 142 /* createAllSmallerThanOrEqualTo */, 1, 14, 1, 15, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig createAllGreaterThan = { { 172 /* createAllGreaterThan */, 1, 16, 1, 17, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig createAllGreaterThanOrEqualTo = { { 193 /* createAllGreaterThanOrEqualTo */, 1, 18, 1, 19, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getMin = { { 223 /* getMin */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getMax = { { 230 /* getMax */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig describe = { { 271 /* describe */, 1, 22, 1, 23, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shift = { { 20 /* shift */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig accepts = { { 331 /* accepts */, 2, 26, 2, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSuperTypeOf = { { 372 /* isSuperTypeOf */, 1, 29, 1, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSubTypeOf = { { 396 /* isSubTypeOf */, 1, 31, 1, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSubTypeOfUnionWithReason = { { 431 /* isSubTypeOfUnionWithReason */, 1, 33, 1, 34, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isAcceptedBy = { { 472 /* isAcceptedBy */, 2, 35, 2, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig equals = { { 485 /* equals */, 1, 38, 1, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig generalize = { { 535 /* generalize */, 1, 40, 1, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSmallerThan = { { 601 /* isSmallerThan */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 615 /* isSmallerThanOrEqual */, 2, 45, 2, 47, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isGreaterThan = { { 636 /* isGreaterThan */, 2, 48, 2, 50, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isGreaterThanOrEqual = { { 650 /* isGreaterThanOrEqual */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerType = { { 671 /* getSmallerType */, 1, 54, 1, 55, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerOrEqualType = { { 686 /* getSmallerOrEqualType */, 1, 56, 1, 57, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterType = { { 708 /* getGreaterType */, 1, 58, 1, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterOrEqualType = { { 723 /* getGreaterOrEqualType */, 1, 60, 1, 61, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBoolean = { { 770 /* toBoolean */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 780 /* toAbsoluteNumber */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 797 /* toBitwiseNotType */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 814 /* toString */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryUnion = { { 823 /* tryUnion */, 1, 66, 1, 67, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryIntersect = { { 832 /* tryIntersect */, 1, 68, 1, 69, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryRemove = { { 858 /* tryRemove */, 1, 70, 1, 71, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 877 /* exponentiate */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 890 /* getFiniteTypes */, 0, 74, 0, 74, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 944 /* toPhpDocNode */, 0, 75, 0, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 957 /* looseCompare */, 2, 76, 2, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig fromFloatComparison = { { 232 /* fromFloatComparison */, 2, 20, 2, 22, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getMin = { { 252 /* getMin */, 0, 23, 0, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMax = { { 259 /* getMax */, 0, 24, 0, 24, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig describe = { { 300 /* describe */, 1, 25, 1, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shift = { { 20 /* shift */, 1, 27, 1, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig accepts = { { 360 /* accepts */, 2, 29, 2, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSuperTypeOf = { { 401 /* isSuperTypeOf */, 1, 32, 1, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSubTypeOf = { { 425 /* isSubTypeOf */, 1, 34, 1, 35, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSubTypeOfUnionWithReason = { { 460 /* isSubTypeOfUnionWithReason */, 1, 36, 1, 37, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isAcceptedBy = { { 501 /* isAcceptedBy */, 2, 38, 2, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig equals = { { 514 /* equals */, 1, 41, 1, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalize = { { 564 /* generalize */, 1, 43, 1, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThan = { { 630 /* isSmallerThan */, 2, 45, 2, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 644 /* isSmallerThanOrEqual */, 2, 48, 2, 50, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isGreaterThan = { { 665 /* isGreaterThan */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isGreaterThanOrEqual = { { 679 /* isGreaterThanOrEqual */, 2, 54, 2, 56, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerType = { { 700 /* getSmallerType */, 1, 57, 1, 58, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerOrEqualType = { { 715 /* getSmallerOrEqualType */, 1, 59, 1, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterType = { { 737 /* getGreaterType */, 1, 61, 1, 62, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterOrEqualType = { { 752 /* getGreaterOrEqualType */, 1, 63, 1, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBoolean = { { 799 /* toBoolean */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 809 /* toAbsoluteNumber */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 826 /* toBitwiseNotType */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 843 /* toString */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryUnion = { { 852 /* tryUnion */, 1, 69, 1, 70, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryIntersect = { { 861 /* tryIntersect */, 1, 71, 1, 72, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryRemove = { { 887 /* tryRemove */, 1, 73, 1, 74, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig exponentiate = { { 906 /* exponentiate */, 1, 75, 1, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 919 /* getFiniteTypes */, 0, 77, 0, 77, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 973 /* toPhpDocNode */, 0, 78, 0, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 986 /* looseCompare */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntegerRangeType diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index 04bf5951fa4..ceab6a10a6b 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -353,14 +353,25 @@ } } } - foreach ([0, 1, -1, 42, PHP_INT_MAX, PHP_INT_MIN, PHP_INT_MAX - 1, PHP_INT_MIN + 1, 0.0, 0.5, -0.5, 2.5, -2.5, 1e30, -1e30, 9.2233720368547758E+18, -9.2233720368547758E+18, 9.2e18, -9.2e18, (float) PHP_INT_MAX, (float) PHP_INT_MIN, NAN, INF, -INF] as $value) { + foreach ([0, 1, -1, 42, PHP_INT_MAX, PHP_INT_MIN, PHP_INT_MAX - 1, PHP_INT_MIN + 1, 0.0, 0.5, -0.5, 2.5, -2.5, 1e30, -1e30, 9.2233720368547758E+18, -9.2233720368547758E+18, 9.2e18, -9.2e18, (float) PHP_INT_MAX, (float) PHP_INT_MIN, PHP_INT_MAX + 1.0, 9223372036854774784.0, 2147483647.0, 2147483646.5, 2147483647.5, -2147483648.0, -2147483648.5, 9007199254740992.0, 9007199254740996.0, -9007199254740996.0, 9223372036854774784.0 - 2048.0, -9223372036854774784.0, NAN, INF, -INF] as $value) { $key = is_float($value) ? var_export($value, true) : (string) $value; foreach (['createAllSmallerThan', 'createAllSmallerThanOrEqualTo', 'createAllGreaterThan', 'createAllGreaterThanOrEqualTo'] as $factory) { $r["$factory $key"] = @$view($rangeClass::$factory($value)); } } - foreach (['5', true, null] as $value) { - foreach (['createAllSmallerThan', 'createAllGreaterThanOrEqualTo'] as $factory) { + $fromFloatComparison = new \ReflectionMethod($rangeClass, 'fromFloatComparison'); + foreach ([2.5, -2.5, 9.2233720368547758E+18, -9.2233720368547758E+18, 9007199254740996.0, NAN] as $value) { + foreach (['<', '<=', '>', '>=', '!='] as $operator) { + $key = 'fromFloatComparison ' . var_export($value, true) . " $operator"; + try { + $r[$key] = $view($fromFloatComparison->invoke(null, $value, $operator)); + } catch (\Throwable $e) { + $r[$key] = get_class($e); + } + } + } + foreach (['5', '1e30', '-1e30', true, null] as $value) { + foreach (['createAllSmallerThan', 'createAllSmallerThanOrEqualTo', 'createAllGreaterThan', 'createAllGreaterThanOrEqualTo'] as $factory) { try { $r["$factory " . var_export($value, true)] = $view($rangeClass::$factory($value)); } catch (\TypeError $e) {