From 9a83d4a3e8b620bcfd30d949dfca10f02ba0300b Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 18:52:07 +0000 Subject: [PATCH 1/4] Describe the numbers a type holds with Type::toNumberSet() The operator helpers read integer and float intervals from the members of a type through a chain of instanceof checks. A NumberSet now carries the integer intervals, the float intervals and NAN of a type, so they read those instead. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/Constant/ConstantFloatType.php | 10 + src/Type/Constant/ConstantIntegerType.php | 6 + src/Type/ExponentiateHelper.php | 15 +- src/Type/FloatRangeType.php | 5 + src/Type/FloatType.php | 5 + src/Type/IntegerRangeType.php | 5 + src/Type/IntegerType.php | 5 + src/Type/NumberSet.php | 54 +++ src/Type/OperatorTypeHelper.php | 148 ++++---- src/Type/Traits/OperatorTypeTrait.php | 6 + src/Type/Type.php | 7 + src/Type/UnionType.php | 19 + tests/PHPStan/Type/NumberSetTest.php | 55 +++ turbo-ext/src/ConstantFloatType.cpp | 15 + turbo-ext/src/ConstantIntegerType.cpp | 12 + turbo-ext/src/FloatRangeType.cpp | 12 + turbo-ext/src/FloatType.cpp | 10 + turbo-ext/src/IntegerRangeType.cpp | 11 + turbo-ext/src/IntegerType.cpp | 9 + turbo-ext/src/NumberSet.cpp | 187 ++++++++++ turbo-ext/src/OperatorTypeHelper.cpp | 328 ++++++++---------- turbo-ext/src/TypeTraits.cpp | 4 + turbo-ext/src/TypeTraits.h | 33 ++ turbo-ext/src/UnionType.cpp | 18 + turbo-ext/src/generated/ConstantFloatType.h | 42 ++- turbo-ext/src/generated/ConstantIntegerType.h | 92 ++--- turbo-ext/src/generated/FloatRangeType.h | 40 ++- turbo-ext/src/generated/FloatType.h | 242 ++++++------- turbo-ext/src/generated/IntegerRangeType.h | 252 +++++++------- turbo-ext/src/generated/IntegerType.h | 152 ++++---- turbo-ext/src/generated/NumberSet.h | 63 ++++ turbo-ext/src/generated/OperatorTypeHelper.h | 224 ++++++------ turbo-ext/src/generated/OperatorTypeTrait.h | 132 +++---- turbo-ext/src/generated/UnionType.h | 220 ++++++------ turbo-ext/tests/smoke.php | 1 + turbo-ext/tests/type-family.php | 10 + 36 files changed, 1511 insertions(+), 938 deletions(-) create mode 100644 src/Type/NumberSet.php create mode 100644 tests/PHPStan/Type/NumberSetTest.php create mode 100644 turbo-ext/src/NumberSet.cpp create mode 100644 turbo-ext/src/generated/NumberSet.h diff --git a/src/Type/Constant/ConstantFloatType.php b/src/Type/Constant/ConstantFloatType.php index cd5a39838ad..008e5cc7468 100644 --- a/src/Type/Constant/ConstantFloatType.php +++ b/src/Type/Constant/ConstantFloatType.php @@ -13,6 +13,7 @@ use PHPStan\Type\FloatType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IsSuperTypeOfResult; +use PHPStan\Type\NumberSet; use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait; use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; @@ -122,6 +123,15 @@ private function toIntegerValue(): int return is_finite($this->value) ? (int) $this->value : 0; } + public function toNumberSet(): NumberSet + { + if (is_nan($this->value)) { + return new NumberSet([], [], true); + } + + return new NumberSet([], [[$this->value, $this->value]], false); + } + public function toInteger(): Type { return new ConstantIntegerType($this->toIntegerValue()); diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 193b89e0914..b9230aa36ca 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -12,6 +12,7 @@ use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IsSuperTypeOfResult; +use PHPStan\Type\NumberSet; use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait; use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; @@ -36,6 +37,11 @@ public function __construct(private int $value) parent::__construct(); } + public function toNumberSet(): NumberSet + { + return new NumberSet([[$this->value, $this->value]], [], false); + } + public function getValue(): int { return $this->value; diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 000a704eaca..eca68df2712 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -139,16 +139,19 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ */ private static function getIntegerBounds(Type $type): ?array { - if ($type instanceof IntegerRangeType) { - return [$type->getMin(), $type->getMax()]; + $numberSet = $type->toNumberSet(); + if ($numberSet === null || $numberSet->getFloatIntervals() !== [] || $numberSet->hasNan()) { + return null; } - $values = $type->getConstantScalarValues(); - if (count($values) === 1 && is_int($values[0])) { - return [$values[0], $values[0]]; + // plain int stays (float|int): the intervals read an open end as no bound + // at all, not as PHP_INT_MIN or PHP_INT_MAX, whose powers overflow + $intervals = $numberSet->getIntegerIntervals(); + if ($intervals === [[null, null]] || count($intervals) !== 1) { + return null; } - return null; + return $intervals[0]; } /** diff --git a/src/Type/FloatRangeType.php b/src/Type/FloatRangeType.php index d582f0fa205..c3393c8e0ae 100644 --- a/src/Type/FloatRangeType.php +++ b/src/Type/FloatRangeType.php @@ -420,6 +420,11 @@ public function toBoolean(): BooleanType return new BooleanType(); } + public function toNumberSet(): NumberSet + { + return new NumberSet([], [[$this->min, $this->max]], false); + } + public function toInteger(): Type { // casting a float outside of the int range is undefined diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index fb6c541dddd..d9f0172303b 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -142,6 +142,11 @@ public function toNumber(): Type return $this; } + public function toNumberSet(): NumberSet + { + return new NumberSet([], [[-INF, INF]], true); + } + public function toBitwiseNotType(): Type { return new IntegerType(); diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index ab18e3c59e6..6b043b9fda3 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -181,6 +181,11 @@ public static function createAllGreaterThanOrEqualTo($value): Type return self::fromInterval(is_nan($value) ? 0 : (int) ceil($value), null); } + public function toNumberSet(): NumberSet + { + return new NumberSet([[$this->min, $this->max]], [], false); + } + public function getMin(): ?int { return $this->min; diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 1264cb42500..4320aa19eee 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -61,6 +61,11 @@ public function toNumber(): Type return $this; } + public function toNumberSet(): NumberSet + { + return new NumberSet([[null, null]], [], false); + } + public function toBitwiseNotType(): Type { return new IntegerType(); diff --git a/src/Type/NumberSet.php b/src/Type/NumberSet.php new file mode 100644 index 00000000000..df13584430e --- /dev/null +++ b/src/Type/NumberSet.php @@ -0,0 +1,54 @@ + $integerIntervals + * @param list $floatIntervals + */ + public function __construct( + private array $integerIntervals, + private array $floatIntervals, + private bool $nan, + ) + { + } + + /** + * @return list + */ + public function getIntegerIntervals(): array + { + return $this->integerIntervals; + } + + /** + * @return list + */ + public function getFloatIntervals(): array + { + return $this->floatIntervals; + } + + public function hasNan(): bool + { + return $this->nan; + } + +} diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 273e9607a3c..56f829d2f09 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -671,12 +671,12 @@ private static function getMaxModuloMagnitude(?int $divisorMin, ?int $divisorMax /** * @return array{int|null, int|null}|null Lowest and highest value the type can hold, with null - * for an unbounded side. Null when the type is not built from integer ranges and constants. + * for an unbounded side. Null when the type holds anything but integers. */ private static function getIntegerBounds(Type $type): ?array { - $innerTypes = $type instanceof UnionType ? $type->getTypes() : [$type]; - if ($innerTypes === []) { + $intervals = self::getIntegerIntervals($type); + if ($intervals === null) { return null; } @@ -684,17 +684,7 @@ private static function getIntegerBounds(Type $type): ?array $max = null; $unboundedMin = false; $unboundedMax = false; - foreach ($innerTypes as $innerType) { - if ($innerType instanceof IntegerRangeType) { - $innerMin = $innerType->getMin(); - $innerMax = $innerType->getMax(); - } elseif ($innerType instanceof ConstantIntegerType) { - $innerMin = $innerType->getValue(); - $innerMax = $innerMin; - } else { - return null; - } - + foreach ($intervals as [$innerMin, $innerMax]) { if ($innerMin === null) { $unboundedMin = true; } elseif ($min === null || $innerMin < $min) { @@ -714,6 +704,25 @@ private static function getIntegerBounds(Type $type): ?array ]; } + /** + * @return non-empty-list|null the integer intervals of a type + * that holds integers and nothing else + */ + private static function getIntegerIntervals(Type $type): ?array + { + $numberSet = $type->toNumberSet(); + if ($numberSet === null || $numberSet->getFloatIntervals() !== [] || $numberSet->hasNan()) { + return null; + } + + $intervals = $numberSet->getIntegerIntervals(); + if ($intervals === []) { + return null; + } + + return $intervals; + } + public static function add(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { $floatRangeResult = self::floatRangeMath('+', $leftType, $rightType, $phpVersion); @@ -1205,17 +1214,17 @@ private static function optimizeScalarType(Type $type): Type */ private static function getNonNegativeIntegerBounds(Type $type): ?array { - if ($type instanceof IntegerRangeType) { - $min = $type->getMin(); - if ($min !== null && $min >= 0) { - return [$min, $type->getMax()]; - } + $intervals = self::getIntegerIntervals($type); + if ($intervals === null || count($intervals) !== 1) { return null; } - if ($type instanceof ConstantIntegerType && $type->getValue() >= 0) { - return [$type->getValue(), $type->getValue()]; + + [$min, $max] = $intervals[0]; + if ($min === null || $min < 0) { + return null; } - return null; + + return [$min, $max]; } /** @@ -1685,8 +1694,9 @@ private static function floatRangeMath(string $operator, Type $leftType, Type $r $hasFloatRange = false; foreach ([$leftMembers, $rightMembers] as $members) { - foreach ($members as [$member]) { - if (!($member instanceof FloatRangeType)) { + foreach ($members as [$integerInterval, [$min, $max, $nan]]) { + // a float member that is neither a constant nor plain float + if ($integerInterval !== null || $min >= $max || $nan) { continue; } @@ -1698,8 +1708,8 @@ private static function floatRangeMath(string $operator, Type $leftType, Type $r } if ($operator === '/') { - foreach ($rightMembers as [$member]) { - if (in_array($member->getConstantScalarValues(), [[0], [0.0]], true)) { + foreach ($rightMembers as [, [$min, $max]]) { + if ($min === 0.0 && $max === 0.0) { return null; } } @@ -1714,9 +1724,11 @@ private static function floatRangeMath(string $operator, Type $leftType, Type $r } $resultTypes = []; - foreach ($leftMembers as [$leftMember, $leftInterval]) { - foreach ($rightMembers as [$rightMember, $rightInterval]) { - if ($leftMember->isInteger()->yes() && $rightMember->isInteger()->yes()) { + foreach ($leftMembers as [$leftInteger, $leftInterval]) { + foreach ($rightMembers as [$rightInteger, $rightInterval]) { + if ($leftInteger !== null && $rightInteger !== null) { + $leftMember = IntegerRangeType::fromInterval($leftInteger[0], $leftInteger[1]); + $rightMember = IntegerRangeType::fromInterval($rightInteger[0], $rightInteger[1]); if ($operator === '+') { $resultTypes[] = self::add($leftMember, $rightMember, $phpVersion); } elseif ($operator === '-') { @@ -1729,7 +1741,7 @@ private static function floatRangeMath(string $operator, Type $leftType, Type $r continue; } - foreach ($operator === '/' && $rightMember->isInteger()->yes() ? self::splitIntegerDivisor($rightInterval) : [$rightInterval] as $divisorInterval) { + foreach ($operator === '/' && $rightInteger !== null ? self::splitIntegerDivisor($rightInterval) : [$rightInterval] as $divisorInterval) { $resultTypes[] = self::floatIntervalMath($operator, $leftInterval, $divisorInterval, $phpVersion); } } @@ -1765,12 +1777,12 @@ private static function splitIntegerDivisor(array $interval): array } /** - * The members of a number type with the floats each of them stands for: - * [min, max, whether NAN, whether an infinite bound is a value]. An int - * without a bound reaches the infinity on that side, which it never is, - * and NAN alone is an empty interval. + * The members of a number type, each with the integers it is (null for a + * float member) and the floats it stands for: [min, max, whether NAN, + * whether an infinite bound is a value]. An int without a bound reaches the + * infinity on that side, which it never is, and NAN alone is an empty interval. * - * @return list|null + * @return list|null */ private static function getFloatIntervalMembers(Type $type): ?array { @@ -1778,36 +1790,37 @@ private static function getFloatIntervalMembers(Type $type): ?array return null; } - $members = []; - foreach ($type instanceof UnionType ? $type->getTypes() : [$type] as $member) { - if ($member instanceof ConstantIntegerType) { - $interval = [(float) $member->getValue(), (float) $member->getValue(), false, false]; - } elseif ($member instanceof IntegerRangeType) { - $interval = [ - $member->getMin() === null ? -INF : (float) $member->getMin(), - $member->getMax() === null ? INF : (float) $member->getMax(), - false, - false, - ]; - } elseif ($member instanceof ConstantFloatType) { - $value = $member->getValue(); - $interval = is_nan($value) ? [INF, -INF, true, true] : [$value, $value, false, true]; - } elseif ($member instanceof FloatRangeType) { - $interval = [$member->getMin(), $member->getMax(), false, true]; - } elseif (get_class($member) === IntegerType::class) { - $interval = [-INF, INF, false, false]; - } elseif (get_class($member) === FloatType::class) { - $interval = [-INF, INF, true, true]; - } else { - return null; - } + $numberSet = $type->toNumberSet(); + if ($numberSet === null) { + return null; + } - $members[] = [$member, $interval]; + $members = []; + foreach ($numberSet->getIntegerIntervals() as $integerInterval) { + [$min, $max] = $integerInterval; + $members[] = [$integerInterval, [$min === null ? -INF : (float) $min, $max === null ? INF : (float) $max, false, false]]; + } + // plain float is one member: the full interval and NAN + $plainFloat = self::isPlainFloat($numberSet); + foreach ($numberSet->getFloatIntervals() as [$min, $max]) { + $members[] = [null, [$min, $max, $plainFloat, true]]; + } + if ($numberSet->hasNan() && !$plainFloat) { + $members[] = [null, [INF, -INF, true, true]]; } return $members; } + /** + * Whether the floats of the set are all of them, which a union holds as one + * FloatType member. + */ + private static function isPlainFloat(NumberSet $numberSet): bool + { + return $numberSet->hasNan() && $numberSet->getFloatIntervals() === [[-INF, INF]]; + } + /** * @param '+'|'-'|'*'|'/' $operator * @param array{float, float, bool, bool} $left @@ -2021,17 +2034,14 @@ public static function intDivide(Type $numeratorType, Type $divisorType, PhpVers */ private static function getIntervals(Type $type): ?array { + $integerIntervals = self::getIntegerIntervals($type); + if ($integerIntervals === null) { + return $type->isInteger()->yes() ? [[-INF, INF]] : null; + } + $intervals = []; - foreach ($type instanceof UnionType ? $type->getTypes() : [$type] as $innerType) { - if ($innerType instanceof ConstantIntegerType) { - $intervals[] = [$innerType->getValue(), $innerType->getValue()]; - } elseif ($innerType instanceof IntegerRangeType) { - $intervals[] = [$innerType->getMin() ?? -INF, $innerType->getMax() ?? INF]; - } elseif ($innerType->isInteger()->yes()) { - $intervals[] = [-INF, INF]; - } else { - return null; - } + foreach ($integerIntervals as [$min, $max]) { + $intervals[] = [$min ?? -INF, $max ?? INF]; } return $intervals; diff --git a/src/Type/Traits/OperatorTypeTrait.php b/src/Type/Traits/OperatorTypeTrait.php index 193f60549fd..b05c20a5f33 100644 --- a/src/Type/Traits/OperatorTypeTrait.php +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -5,6 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; use PHPStan\Type\LooseComparisonHelper; +use PHPStan\Type\NumberSet; use PHPStan\Type\OperatorTypeHelper; use PHPStan\Type\Type; @@ -16,6 +17,11 @@ public function exponentiate(Type $exponent): Type return $this->arithmeticPower($exponent, new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION)); } + public function toNumberSet(): ?NumberSet + { + return null; + } + public function arithmeticAdd(Type $right, PhpVersion $phpVersion): Type { return OperatorTypeHelper::add($this, $right, $phpVersion); diff --git a/src/Type/Type.php b/src/Type/Type.php index aba74d884fe..2da53cc9cfc 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -454,6 +454,13 @@ public function toBoolean(): BooleanType; /** Models numeric coercion for arithmetic operators. */ public function toNumber(): Type; + /** + * The intervals of the numbers this type holds, or null when it can hold + * anything but an int or a float. Unlike toNumber() it converts nothing: + * call it on toNumber() for the numbers an operand stands for. + */ + public function toNumberSet(): ?NumberSet; + /** Models the bitwise-not (`~$x`) operator. Returns `ErrorType` for types where `~` is undefined. */ public function toBitwiseNotType(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f76a7fa5710..36828247a7b 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -1269,6 +1269,25 @@ public function toNumber(): Type return $type; } + public function toNumberSet(): ?NumberSet + { + $integerIntervals = []; + $floatIntervals = []; + $nan = false; + foreach ($this->types as $type) { + $numberSet = $type->toNumberSet(); + if ($numberSet === null) { + return null; + } + + $integerIntervals = array_merge($integerIntervals, $numberSet->getIntegerIntervals()); + $floatIntervals = array_merge($floatIntervals, $numberSet->getFloatIntervals()); + $nan = $nan || $numberSet->hasNan(); + } + + return new NumberSet($integerIntervals, $floatIntervals, $nan); + } + public function toBitwiseNotType(): Type { return $this->unionTypes(static fn (Type $type): Type => $type->toBitwiseNotType()); diff --git a/tests/PHPStan/Type/NumberSetTest.php b/tests/PHPStan/Type/NumberSetTest.php new file mode 100644 index 00000000000..a667f61bec7 --- /dev/null +++ b/tests/PHPStan/Type/NumberSetTest.php @@ -0,0 +1,55 @@ +, list, bool}|null}> + */ + public static function dataToNumberSet(): iterable + { + yield ['int', [[[null, null]], [], false]]; + yield ['int', [[[null, 0]], [], false]]; + yield ['7', [[[7, 7]], [], false]]; + yield ['int<1, 5>|7', [[[1, 5], [7, 7]], [], false]]; + yield ['float', [[], [[-INF, INF]], true]]; + yield ['float', [[], [[-INF, INF]], false]]; + yield ['NAN', [[], [], true]]; + yield ['-1.5', [[], [[-1.5, -1.5]], false]]; + yield ['float<1.5, 2>|NAN|3', [[[3, 3]], [[1.5, 2.0]], true]]; + yield ['int|float<0, max>', [[[null, 2]], [[0.0, INF]], false]]; + yield ['string', null]; + yield ['int|string', null]; + yield ['1|null', null]; + yield ['mixed', null]; + } + + /** + * @param array{list, list, bool}|null $expected + */ + #[DataProvider('dataToNumberSet')] + public function testToNumberSet(string $typeString, ?array $expected): void + { + $type = self::getContainer()->getByType(TypeStringResolver::class)->resolve($typeString); + $numberSet = $type->toNumberSet(); + if ($expected === null) { + $this->assertNull($numberSet, $type->describe(VerbosityLevel::precise())); + return; + } + + $this->assertNotNull($numberSet, $type->describe(VerbosityLevel::precise())); + $this->assertSame($expected, [ + $numberSet->getIntegerIntervals(), + $numberSet->getFloatIntervals(), + $numberSet->hasNan(), + ]); + } + +} diff --git a/turbo-ext/src/ConstantFloatType.cpp b/turbo-ext/src/ConstantFloatType.cpp index 2fcef0a6bc5..af15a851b46 100644 --- a/turbo-ext/src/ConstantFloatType.cpp +++ b/turbo-ext/src/ConstantFloatType.cpp @@ -75,6 +75,20 @@ class ConstantFloatType bool getValue(double &out) const { return value(out); } + /* NAN alone, or the point of the value; UNDEF = pending exception */ + zv::Val toNumberSet() const + { + double v; + if (UNEXPECTED(!value(v))) return zv::Val(); + NumberSetData data; + if (std::isnan(v)) { + data.nan = true; + } else { + data.floatIntervals.push_back({v, v}); + } + return pt_number_set_new(data); + } + /* $type instanceof self && ($this->value === $type->value || both NAN) * — the private slot declared here, read directly on a subclass too; * false = pending exception */ @@ -338,6 +352,7 @@ PT_MINIT_REGISTRATION(pt_register_constant_float_type) cls.method<&ConstantFloatType::toBoolean>(sigs::toBoolean); cls.method<&ConstantFloatType::toInteger>(sigs::toInteger); + cls.method<&ConstantFloatType::toNumberSet>(sigs::toNumberSet); cls.method<&ConstantFloatType::toBitwiseNotType>(sigs::toBitwiseNotType); diff --git a/turbo-ext/src/ConstantIntegerType.cpp b/turbo-ext/src/ConstantIntegerType.cpp index 983a686470c..f1ad5a38f16 100644 --- a/turbo-ext/src/ConstantIntegerType.cpp +++ b/turbo-ext/src/ConstantIntegerType.cpp @@ -68,6 +68,17 @@ class ConstantIntegerType bool getValue(zend_long &out) const { return value(out); } + /* new NumberSet([[$this->value, $this->value]], [], false); UNDEF = + * pending exception */ + zv::Val toNumberSet() const + { + zend_long v; + if (UNEXPECTED(!value(v))) return zv::Val(); + NumberSetData data; + data.integerIntervals.push_back({NullableLong::of(v), NullableLong::of(v)}); + return pt_number_set_new(data); + } + /* yes/no against another ConstantIntegerType's value, maybe/no against * an IntegerRangeType's bounds, maybe for any other IntegerType, the * CompoundType callback, no otherwise; UNDEF = pending exception */ @@ -280,6 +291,7 @@ PT_MINIT_REGISTRATION(pt_register_constant_integer_type) cls.method<&ConstantIntegerType::toAbsoluteNumber>(sigs::toAbsoluteNumber); cls.method<&ConstantIntegerType::toString>(sigs::toString); + cls.method<&ConstantIntegerType::toNumberSet>(sigs::toNumberSet); cls.method<&ConstantIntegerType::toArrayKey>(sigs::toArrayKey); cls.op(); diff --git a/turbo-ext/src/FloatRangeType.cpp b/turbo-ext/src/FloatRangeType.cpp index d2c79334864..b1244cdf921 100644 --- a/turbo-ext/src/FloatRangeType.cpp +++ b/turbo-ext/src/FloatRangeType.cpp @@ -173,6 +173,17 @@ class FloatRangeType return slot(slots::min, "min", min) && slot(slots::max, "max", max); } + /* new NumberSet([], [[$this->min, $this->max]], false); UNDEF = pending + * exception */ + zv::Val toNumberSet() const + { + double min = 0.0, max = 0.0; + if (UNEXPECTED(!bounds(min, max))) return zv::Val(); + NumberSetData data; + data.floatIntervals.push_back({min, max}); + return pt_number_set_new(data); + } + bool getMin(double &out) const { return slot(slots::min, "min", out); } bool getMax(double &out) const { return slot(slots::max, "max", out); } @@ -970,6 +981,7 @@ PT_MINIT_REGISTRATION(pt_register_float_range_type) pt_frt_bound(INTERNAL_FUNCTION_PARAM_PASSTHRU, &FloatRangeType::getMax); }); + cls.method<&FloatRangeType::toNumberSet>(sigs::toNumberSet); cls.method<&FloatRangeType::describe, zp::Obj>(sigs::describe); cls.op(); diff --git a/turbo-ext/src/FloatType.cpp b/turbo-ext/src/FloatType.cpp index 42ecdbeb580..fa4ffd86c02 100644 --- a/turbo-ext/src/FloatType.cpp +++ b/turbo-ext/src/FloatType.cpp @@ -118,6 +118,15 @@ class FloatType zv::Val toFloat() const { return thisValue(); } /* new IntegerType() */ + /* new NumberSet([], [[-INF, INF]], true) */ + static zv::Val toNumberSet() + { + NumberSetData data; + data.floatIntervals.push_back({-HUGE_VAL, HUGE_VAL}); + data.nan = true; + return pt_number_set_new(data); + } + static zv::Val toInteger() { return integerType(); } /* new IntersectionType([new StringType(), new AccessoryNonEmptyStringType(), @@ -307,6 +316,7 @@ PT_MINIT_REGISTRATION(pt_register_float_type) cls.method<&FloatType::toFloat>(sigs::toFloat); cls.method<&FloatType::toInteger>(sigs::toInteger); + cls.method<&FloatType::toNumberSet>(sigs::toNumberSet); cls.method<&FloatType::toString>(sigs::toString); diff --git a/turbo-ext/src/IntegerRangeType.cpp b/turbo-ext/src/IntegerRangeType.cpp index 4837b489c19..2c2a4a200a9 100644 --- a/turbo-ext/src/IntegerRangeType.cpp +++ b/turbo-ext/src/IntegerRangeType.cpp @@ -709,6 +709,16 @@ class IntegerRangeType /* true when 0 is out of the range, bool when it may be in, false when * the range is 0 alone — which fromInterval() never builds, so the * maybe branch is the one a range takes; UNDEF = pending exception */ + /* new NumberSet([[$this->min, $this->max]], [], false); UNDEF = pending + * exception */ + zv::Val toNumberSet() const + { + NumberSetData data; + data.integerIntervals.push_back({NullableLong::null(), NullableLong::null()}); + if (UNEXPECTED(!bounds(data.integerIntervals[0].min, data.integerIntervals[0].max))) return zv::Val(); + return pt_number_set_new(data); + } + zv::Val toBoolean() const { zend_long isZero; @@ -1512,6 +1522,7 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) cls.method<&IntegerRangeType::toFloat>(sigs::toFloat); cls.method<&IntegerRangeType::toBoolean>(sigs::toBoolean); + cls.method<&IntegerRangeType::toNumberSet>(sigs::toNumberSet); cls.method<&IntegerRangeType::toAbsoluteNumber>(sigs::toAbsoluteNumber); diff --git a/turbo-ext/src/IntegerType.cpp b/turbo-ext/src/IntegerType.cpp index e031c105de4..2cca1c6dea2 100644 --- a/turbo-ext/src/IntegerType.cpp +++ b/turbo-ext/src/IntegerType.cpp @@ -33,6 +33,14 @@ class IntegerType /* $this */ zv::Val toNumber() const { return thisValue(); } + /* new NumberSet([[null, null]], [], false) */ + static zv::Val toNumberSet() + { + NumberSetData data; + data.integerIntervals.push_back({NullableLong::null(), NullableLong::null()}); + return pt_number_set_new(data); + } + /* new IntegerType() */ static zv::Val toBitwiseNotType() { return create(); } @@ -257,6 +265,7 @@ PT_MINIT_REGISTRATION(pt_register_integer_type) cls.method<&IntegerType::getConstantStrings>(sigs::getConstantStrings); cls.method<&IntegerType::toNumber>(sigs::toNumber); + cls.method<&IntegerType::toNumberSet>(sigs::toNumberSet); cls.method<&IntegerType::toBitwiseNotType>(sigs::toBitwiseNotType); diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp new file mode 100644 index 00000000000..a68691b617e --- /dev/null +++ b/turbo-ext/src/NumberSet.cpp @@ -0,0 +1,187 @@ +/* + * PHPStanTurbo\NumberSet — native implementation of PHPStan\Type\NumberSet. + * + * Declared as PHPStan\Type\NumberSet itself at activation: final, no parent, + * no interfaces. State is the twin's `private array $integerIntervals`, + * `private array $floatIntervals` and `private bool $nan`, declared typed + * property slots, so the std object handlers do GC/clone. + * + * pt_number_set_new() and pt_type_to_number_set() are the native producers' + * and consumers' way in and out of the PHP arrays, so the operator helpers + * work on NumberSetData instead. + */ + +#include "TypeTraits.h" +#include "generated/NumberSet.h" + +namespace slots = ptdecl::NumberSet::slot; +namespace sigs = ptdecl::NumberSet::sig; + +static zend_class_entry *pt_ce_number_set = nullptr; + +namespace phpstanturbo { + +/* Mirrors PHPStan\Type\NumberSet. */ +class NumberSet +{ +public: + explicit NumberSet(zend_object *self) : self(self) {} + + /* __construct(private array $integerIntervals, private array + * $floatIntervals, private bool $nan) */ + void construct(zval *integerIntervals, zval *floatIntervals, bool nan) + { + zval *integerSlot = OBJ_PROP_NUM(self, slots::integerIntervals); + zval *floatSlot = OBJ_PROP_NUM(self, slots::floatIntervals); + zval *nanSlot = OBJ_PROP_NUM(self, slots::nan); + zval_ptr_dtor(integerSlot); + zval_ptr_dtor(floatSlot); + ZVAL_COPY(integerSlot, integerIntervals); + ZVAL_COPY(floatSlot, floatIntervals); + ZVAL_BOOL(nanSlot, nan); + Z_PROP_FLAG_P(integerSlot) = 0; /* no longer IS_PROP_UNINIT */ + Z_PROP_FLAG_P(floatSlot) = 0; + Z_PROP_FLAG_P(nanSlot) = 0; + } + + static zv::Val create(const NumberSetData &data) + { + zv::Arr integerIntervals = zv::Arr::create((uint32_t) data.integerIntervals.size()); + for (const NumberSetData::IntegerInterval &interval : data.integerIntervals) { + zv::Arr pair = zv::Arr::create(2); + pair.push(interval.min.toVal()); + pair.push(interval.max.toVal()); + integerIntervals.push(zv::Val(std::move(pair))); + } + zv::Arr floatIntervals = zv::Arr::create((uint32_t) data.floatIntervals.size()); + for (const NumberSetData::FloatInterval &interval : data.floatIntervals) { + zv::Arr pair = zv::Arr::create(2); + pair.push(doubleValue(interval.min)); + pair.push(doubleValue(interval.max)); + floatIntervals.push(zv::Val(std::move(pair))); + } + + zval object; + if (UNEXPECTED(object_init_ex(&object, pt_ce_number_set) != SUCCESS)) return zv::Val(); + NumberSet(Z_OBJ(object)).construct(integerIntervals.raw(), floatIntervals.raw(), data.nan); + return zv::Val::adopt(object); + } + + /* the slots read into `out`; false = pending exception */ + [[nodiscard]] bool read(NumberSetData &out) const + { + zval *integerSlot, *floatSlot, *nanSlot; + if (UNEXPECTED(!slot(slots::integerIntervals, "integerIntervals", integerSlot))) return false; + if (UNEXPECTED(!slot(slots::floatIntervals, "floatIntervals", floatSlot))) return false; + if (UNEXPECTED(!slot(slots::nan, "nan", nanSlot))) return false; + + for (zv::ArrayEntry entry : zv::ArrRef(integerSlot)) { + zval *pair = entry.value().deref().raw(); + out.integerIntervals.push_back({ + NullableLong::from(zend_hash_index_find(Z_ARRVAL_P(pair), 0)), + NullableLong::from(zend_hash_index_find(Z_ARRVAL_P(pair), 1)), + }); + } + for (zv::ArrayEntry entry : zv::ArrRef(floatSlot)) { + zval *pair = entry.value().deref().raw(); + out.floatIntervals.push_back({ + zval_get_double(zend_hash_index_find(Z_ARRVAL_P(pair), 0)), + zval_get_double(zend_hash_index_find(Z_ARRVAL_P(pair), 1)), + }); + } + out.nan = out.nan || Z_TYPE_P(nanSlot) == IS_TRUE; + return true; + } + + /* a copy of the slot; UNDEF = pending exception */ + zv::Val get(uint32_t index, const char *name) const + { + zval *value; + if (UNEXPECTED(!slot(index, name, value))) return zv::Val(); + return zv::Val::copyOf(zv::Ref(value)); + } + +private: + zend_object *self; + + static zv::Val doubleValue(double value) + { + zval z; + ZVAL_DOUBLE(&z, value); + return zv::Val::adopt(z); + } + + bool slot(uint32_t index, const char *name, zval *&out) const + { + zval *value = OBJ_PROP_NUM(self, index); + if (EXPECTED(Z_TYPE_P(value) != IS_UNDEF)) { + out = value; + return true; + } + zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization", ZSTR_VAL(pt_ce_number_set->name), name); + return false; + } +}; + +} // namespace phpstanturbo + +using phpstanturbo::NumberSet; +using phpstanturbo::NumberSetData; + +zv::Val pt_number_set_new(const NumberSetData &data) +{ + return NumberSet::create(data); +} + +bool pt_number_set_read(zend_object *numberSet, NumberSetData &out) +{ + return NumberSet(numberSet).read(out); +} + +bool pt_type_to_number_set(zval *type, NumberSetData &out, bool &has) +{ + has = false; + zv::Val numberSet = pt_type_call(Z_OBJ_P(type), PT_LC("tonumberset"), 0, NULL); + if (UNEXPECTED(numberSet.isUndef())) return false; + if (Z_TYPE_P(numberSet.raw()) != IS_OBJECT) return true; + + if (UNEXPECTED(!pt_number_set_read(Z_OBJ_P(numberSet.raw()), out))) return false; + has = true; + return true; +} + +/* {{{ engine ABI glue: parameter parsing + registration */ + +#define PT_THIS NumberSet(Z_OBJ_P(ZEND_THIS)) + +PT_MINIT_REGISTRATION(pt_register_number_set) +{ + reg::Class cls("PHPStan\\Type\\NumberSet"); + ptdecl::NumberSet::declareClass(cls); + /* "integerIntervals", "floatIntervals" and "nan" must stay the declared + * properties in this order (slots::*) */ + ptdecl::NumberSet::declareProperties(cls); + + cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *integerIntervals, *floatIntervals; + bool nan; + if (!zp::parse(execute_data, integerIntervals, floatIntervals, nan)) RETURN_THROWS(); + PT_THIS.construct(integerIntervals, floatIntervals, nan); + }); + cls.method(sigs::getIntegerIntervals, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(PT_THIS.get(slots::integerIntervals, "integerIntervals")); + }); + cls.method(sigs::getFloatIntervals, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(PT_THIS.get(slots::floatIntervals, "floatIntervals")); + }); + cls.method(sigs::hasNan, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(PT_THIS.get(slots::nan, "nan")); + }); + + cls.shadow(&pt_ce_number_set); +} + +/* }}} */ diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 3dce752a4eb..a68cdc733d7 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -781,74 +781,83 @@ class OperatorTypeHelper } /* private: the lowest and highest value the type can hold, null for an - * unbounded side; `has` false when the type is not built from integer - * ranges and constants. false = pending exception */ + * unbounded side; `has` false when the type holds anything but + * integers. false = pending exception */ [[nodiscard]] static bool getIntegerBounds(zval *type, bool &has, NullableLong &outMin, NullableLong &outMax) { - has = false; - zv::Val innerTypesHold; - zval single; - HashTable *innerTypes = NULL; - if (isA(type, pt_ce_union_type)) { - zv::Val types = callOn(type, mGetTypes); - if (UNEXPECTED(types.isUndef() || !requireArray(types.raw()))) return false; - innerTypesHold = std::move(types); - innerTypes = Z_ARRVAL_P(innerTypesHold.raw()); - if (zend_hash_num_elements(innerTypes) == 0) return true; - } + std::vector intervals; + if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return false; + if (!has) return true; NullableLong min = NullableLong::null(); NullableLong max = NullableLong::null(); bool unboundedMin = false; bool unboundedMax = false; - auto visit = [&](zval *innerType, bool &known) -> bool { - NullableLong innerMin, innerMax; - known = true; - if (isA(innerType, pt_ce_integer_range_type)) { - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(innerType), innerMin, innerMax))) return false; - } else if (isA(innerType, pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(innerType), value))) return false; - innerMin = NullableLong::of(value); - innerMax = innerMin; - } else { - known = false; - return true; - } - - if (innerMin.isNull) { + for (const NumberSetData::IntegerInterval &interval : intervals) { + if (interval.min.isNull) { unboundedMin = true; - } else if (min.isNull || innerMin.value < min.value) { - min = innerMin; + } else if (min.isNull || interval.min.value < min.value) { + min = interval.min; } - if (innerMax.isNull) { + if (interval.max.isNull) { unboundedMax = true; - } else if (max.isNull || innerMax.value > max.value) { - max = innerMax; - } - return true; - }; - - if (innerTypes == NULL) { - ZVAL_COPY_VALUE(&single, type); - bool known; - if (UNEXPECTED(!visit(&single, known))) return false; - if (!known) return true; - } else { - for (zv::ArrayEntry entry : zv::TableRef(innerTypes)) { - bool known; - if (UNEXPECTED(!visit(entry.value().deref().raw(), known))) return false; - if (!known) return true; + } else if (max.isNull || interval.max.value > max.value) { + max = interval.max; } } - has = true; outMin = unboundedMin ? NullableLong::null() : min; outMax = unboundedMax ? NullableLong::null() : max; return true; } + /* Mirrors getIntegerIntervals(): the integer intervals of a type that + * holds integers and nothing else, has = false for its null; false = + * pending exception */ + [[nodiscard]] static bool getIntegerIntervals(zval *type, std::vector &intervals, bool &has) + { + NumberSetData numberSet; + if (UNEXPECTED(!pt_type_to_number_set(type, numberSet, has))) return false; + if (!has) return true; + + if (!numberSet.floatIntervals.empty() || numberSet.nan || numberSet.integerIntervals.empty()) { + has = false; + return true; + } + + intervals = std::move(numberSet.integerIntervals); + return true; + } + + /* getIntegerIntervals() as the private method returns it: a list of + * [min, max] pairs or null; UNDEF = pending exception */ + static zv::Val getIntegerIntervalsValue(zval *type) + { + std::vector intervals; + bool has = false; + if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return zv::Val(); + if (!has) return zv::Val::null(); + return integerIntervalsValue(intervals); + } + + static zv::Val integerIntervalsValue(const std::vector &intervals) + { + zv::Arr list = zv::Arr::create((uint32_t) intervals.size()); + for (const NumberSetData::IntegerInterval &interval : intervals) { + list.push(integerIntervalValue(interval)); + } + return zv::Val(std::move(list)); + } + + static zv::Val integerIntervalValue(const NumberSetData::IntegerInterval &interval) + { + zv::Arr pair = zv::Arr::create(2); + pair.push(interval.min.toVal()); + pair.push(interval.max.toVal()); + return zv::Val(std::move(pair)); + } + /* getIntegerBounds() as the private method returns it: the [min, max] * pair or null; UNDEF = pending exception */ static zv::Val getIntegerBoundsValue(zval *type) @@ -1322,26 +1331,17 @@ class OperatorTypeHelper * for no finite upper bound), false = null; -1 = pending exception */ static int getNonNegativeIntegerBounds(zval *type, zend_long &min, NullableLong &max) { - if (isA(type, pt_ce_integer_range_type)) { - NullableLong rangeMin, rangeMax; - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(type), rangeMin, rangeMax))) return -1; - if (!rangeMin.isNull && rangeMin.value >= 0) { - min = rangeMin.value; - max = rangeMax; - return 1; - } - return 0; - } - if (isA(type, pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(type), value))) return -1; - if (value >= 0) { - min = value; - max = NullableLong::of(value); - return 1; - } - } - return 0; + std::vector intervals; + bool has = false; + if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return -1; + if (!has || intervals.size() != 1) return 0; + + const NumberSetData::IntegerInterval &interval = intervals[0]; + if (interval.min.isNull || interval.min.value < 0) return 0; + + min = interval.min.value; + max = interval.max; + return 1; } /* Mirrors getNonNegativeIntegerBounds() as the PHP value it returns: @@ -1901,9 +1901,12 @@ class OperatorTypeHelper bool infinities; }; + /* a member with the integers it is (none for a float member) and the + * floats it stands for */ struct FloatIntervalMember { - zv::Val type; + bool isInteger; + NumberSetData::IntegerInterval integers; FloatInterval interval; }; @@ -1923,20 +1926,16 @@ class OperatorTypeHelper bool hasFloatRange = false; for (std::vector *members : {&leftMembers, &rightMembers}) { for (FloatIntervalMember &member : *members) { - if (isA(member.type.raw(), pt_ce_float_range_type)) { - hasFloatRange = true; - } + // a float member that is neither a constant nor plain float + if (member.isInteger || member.interval.min >= member.interval.max || member.interval.nan) continue; + hasFloatRange = true; } } if (!hasFloatRange) return zv::Val::null(); if (kind == IETR_DIV) { for (FloatIntervalMember &member : rightMembers) { - IETR_VAL(values, callOn(member.type.raw(), mGetConstantScalarValues)); - if (UNEXPECTED(!requireArray(values.raw()))) return zv::Val(); - if (zend_hash_num_elements(Z_ARRVAL_P(values.raw())) != 1) continue; - zval *value = zend_hash_index_find(Z_ARRVAL_P(values.raw()), 0); - if (value != NULL && isZeroNumber(value)) return zv::Val::null(); + if (member.interval.min == 0.0 && member.interval.max == 0.0) return zv::Val::null(); } } @@ -1952,29 +1951,23 @@ class OperatorTypeHelper zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftMembers.size() * rightMembers.size())); for (FloatIntervalMember &leftMember : leftMembers) { for (FloatIntervalMember &rightMember : rightMembers) { - IETR_TRI(leftIsInteger, triOn(leftMember.type.raw(), mIsInteger)); - if (leftIsInteger == PT_TRI_YES) { - IETR_TRI(rightIsInteger, triOn(rightMember.type.raw(), mIsInteger)); - if (rightIsInteger == PT_TRI_YES) { - zv::Val pairResult; - if (kind == IETR_PLUS) { - pairResult = add(leftMember.type.raw(), rightMember.type.raw(), phpVersion); - } else if (kind == IETR_DIV) { - pairResult = divide(leftMember.type.raw(), rightMember.type.raw(), phpVersion); - } else { - pairResult = subtractOrMultiply(kind, leftMember.type.raw(), rightMember.type.raw(), phpVersion); - } - if (UNEXPECTED(pairResult.isUndef())) return zv::Val(); - resultTypes.push(std::move(pairResult)); - continue; + if (leftMember.isInteger && rightMember.isInteger) { + IETR_VAL(leftType_, pt_integer_range_from_interval(leftMember.integers.min, leftMember.integers.max, 0)); + IETR_VAL(rightType_, pt_integer_range_from_interval(rightMember.integers.min, rightMember.integers.max, 0)); + zv::Val pairResult; + if (kind == IETR_PLUS) { + pairResult = add(leftType_.raw(), rightType_.raw(), phpVersion); + } else if (kind == IETR_DIV) { + pairResult = divide(leftType_.raw(), rightType_.raw(), phpVersion); + } else { + pairResult = subtractOrMultiply(kind, leftType_.raw(), rightType_.raw(), phpVersion); } + if (UNEXPECTED(pairResult.isUndef())) return zv::Val(); + resultTypes.push(std::move(pairResult)); + continue; } - bool splitDivisor = false; - if (kind == IETR_DIV) { - IETR_TRI(rightIsInteger, triOn(rightMember.type.raw(), mIsInteger)); - splitDivisor = rightIsInteger == PT_TRI_YES; - } + bool splitDivisor = kind == IETR_DIV && rightMember.isInteger; FloatInterval divisorIntervals[3]; size_t divisorCount = splitDivisor ? splitIntegerDivisor(rightMember.interval, divisorIntervals) : 1; if (!splitDivisor) divisorIntervals[0] = rightMember.interval; @@ -1995,56 +1988,40 @@ class OperatorTypeHelper has = false; if (isA(type, pt_ce_benevolent_union_type)) return true; - zv::Val innerTypes; - if (isA(type, pt_ce_union_type)) { - innerTypes = callOn(type, mGetTypes); - if (UNEXPECTED(innerTypes.isUndef() || !requireArray(innerTypes.raw()))) return false; - } else { - zv::Arr single = zv::Arr::create(1); - single.push(zv::Ref(type)); - innerTypes = zv::Val(std::move(single)); - } + NumberSetData numberSet; + if (UNEXPECTED(!pt_type_to_number_set(type, numberSet, has))) return false; + if (!has) return true; - for (zv::ArrayEntry entry : zv::ArrRef(innerTypes.raw())) { - zval *member = entry.value().raw(); - FloatInterval interval; - if (isA(member, pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(member), value))) return false; - interval = {static_cast(value), static_cast(value), false, false}; - } else if (isA(member, pt_ce_integer_range_type)) { - NullableLong min, max; - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(member), min, max))) return false; - interval = { - min.isNull ? -HUGE_VAL : static_cast(min.value), - max.isNull ? HUGE_VAL : static_cast(max.value), - false, - false, - }; - } else if (isA(member, pt_ce_constant_float_type)) { - double value; - if (UNEXPECTED(!pt_constant_float_get_value(Z_OBJ_P(member), value))) return false; - interval = std::isnan(value) ? FloatInterval{HUGE_VAL, -HUGE_VAL, true, true} : FloatInterval{value, value, false, true}; - } else if (isA(member, pt_ce_float_range_type)) { - double min, max; - if (UNEXPECTED(!pt_float_range_bounds(Z_OBJ_P(member), min, max))) return false; - interval = {min, max, false, true}; - } else if (Z_TYPE_P(member) == IS_OBJECT && Z_OBJCE_P(member) == pt_ce_integer_type) { - interval = {-HUGE_VAL, HUGE_VAL, false, false}; - } else if (Z_TYPE_P(member) == IS_OBJECT && Z_OBJCE_P(member) == pt_ce_float_type) { - interval = {-HUGE_VAL, HUGE_VAL, true, true}; - } else { - members.clear(); - return true; - } - - members.push_back({zv::Val::copyOf(zv::Ref(member)), interval}); + for (const NumberSetData::IntegerInterval &integers : numberSet.integerIntervals) { + members.push_back({true, integers, { + integers.min.isNull ? -HUGE_VAL : static_cast(integers.min.value), + integers.max.isNull ? HUGE_VAL : static_cast(integers.max.value), + false, + false, + }}); + } + // plain float is one member: the full interval and NAN + bool plainFloat = isPlainFloat(numberSet); + NumberSetData::IntegerInterval noIntegers = {NullableLong::null(), NullableLong::null()}; + for (const NumberSetData::FloatInterval &floats : numberSet.floatIntervals) { + members.push_back({false, noIntegers, {floats.min, floats.max, plainFloat, true}}); + } + if (numberSet.nan && !plainFloat) { + members.push_back({false, noIntegers, {HUGE_VAL, -HUGE_VAL, true, true}}); } - has = true; return true; } + /* Mirrors isPlainFloat(). */ + static bool isPlainFloat(const NumberSetData &numberSet) + { + return numberSet.nan + && numberSet.floatIntervals.size() == 1 + && numberSet.floatIntervals[0].min == -HUGE_VAL + && numberSet.floatIntervals[0].max == HUGE_VAL; + } + /* getFloatIntervalMembers() for the private method's glue */ static zv::Val getFloatIntervalMembersValue(zval *type) { @@ -2056,7 +2033,7 @@ class OperatorTypeHelper zv::Arr list = zv::Arr::create((uint32_t) members.size()); for (FloatIntervalMember &member : members) { zv::Arr pair = zv::Arr::create(2); - pair.push(std::move(member.type)); + pair.push(member.isInteger ? integerIntervalValue(member.integers) : zv::Val::null()); pair.push(floatIntervalValue(member.interval)); list.push(zv::Val(std::move(pair))); } @@ -2445,52 +2422,23 @@ class OperatorTypeHelper [[nodiscard]] static bool getIntervals(zval *type, std::vector &intervals, bool &known) { known = false; - zv::Val typesHold; - zval single; - HashTable *innerTypes; - if (isA(type, pt_ce_union_type)) { - typesHold = callOn(type, mGetTypes); - if (UNEXPECTED(typesHold.isUndef() || !requireArray(typesHold.raw()))) return false; - innerTypes = Z_ARRVAL_P(typesHold.raw()); - } else { - innerTypes = NULL; - ZVAL_COPY_VALUE(&single, type); - } - - auto visit = [&](zval *innerType, bool &ok) -> bool { - ok = true; - if (isA(innerType, pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(innerType), value))) return false; - intervals.push_back({ Bound::of(value), Bound::of(value) }); - return true; - } - if (isA(innerType, pt_ce_integer_range_type)) { - NullableLong min, max; - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(innerType), min, max))) return false; - intervals.push_back({ min.isNull ? Bound::infinity(-1) : Bound::of(min.value), max.isNull ? Bound::infinity(1) : Bound::of(max.value) }); - return true; - } - zend_long isInteger = triOn(innerType, mIsInteger); + std::vector integerIntervals; + bool has = false; + if (UNEXPECTED(!getIntegerIntervals(type, integerIntervals, has))) return false; + if (!has) { + zend_long isInteger = triOn(type, mIsInteger); if (UNEXPECTED(isInteger < 0)) return false; - if (isInteger == PT_TRI_YES) { - intervals.push_back({ Bound::infinity(-1), Bound::infinity(1) }); - return true; - } - ok = false; + if (isInteger != PT_TRI_YES) return true; + intervals.push_back({ Bound::infinity(-1), Bound::infinity(1) }); + known = true; return true; - }; + } - if (innerTypes == NULL) { - bool ok; - if (UNEXPECTED(!visit(&single, ok))) return false; - if (!ok) return true; - } else { - for (zv::ArrayEntry entry : zv::TableRef(innerTypes)) { - bool ok; - if (UNEXPECTED(!visit(entry.value().deref().raw(), ok))) return false; - if (!ok) return true; - } + for (const NumberSetData::IntegerInterval &interval : integerIntervals) { + intervals.push_back({ + interval.min.isNull ? Bound::infinity(-1) : Bound::of(interval.min.value), + interval.max.isNull ? Bound::infinity(1) : Bound::of(interval.max.value), + }); } known = true; @@ -2765,6 +2713,7 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) }); cls.method(sigs::getIntegerBounds, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerBoundsValue(type))); + cls.method(sigs::getIntegerIntervals, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerIntervalsValue(type))); cls.method(sigs::add, OT_BINARY_GLUE(OperatorTypeHelper::add(leftType, rightType, phpVersion))); cls.method(sigs::arrayUnion, OT_BINARY_GLUE(OperatorTypeHelper::arrayUnion(leftType, rightType))); cls.method(sigs::tryArrayUnion, OT_TYPES_GLUE(OperatorTypeHelper::tryArrayUnion(leftType, rightType))); @@ -2869,6 +2818,13 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) }); cls.method(sigs::getFloatIntervalMembers, OT_TYPE_GLUE(OperatorTypeHelper::getFloatIntervalMembersValue(type))); + cls.method(sigs::isPlainFloat, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *numberSet; + if (!zp::parse(execute_data, numberSet)) RETURN_THROWS(); + phpstanturbo::NumberSetData data; + if (UNEXPECTED(!pt_number_set_read(Z_OBJ_P(numberSet), data))) RETURN_THROWS(); + RETURN_BOOL(OperatorTypeHelper::isPlainFloat(data)); + }); cls.method(sigs::splitIntegerDivisor, [](INTERNAL_FUNCTION_PARAMETERS) { zval *interval; diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index b9c83a23be4..7a0484f0482 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -5457,6 +5457,10 @@ void pt_type_trait_operator(reg::Class &cls) zv::Args argv{exponent, phpVersion.raw()}; PT_RETURN_VAL(pt_type_call(PT_THIS_OBJ, PT_LC("arithmeticpower"), 2, argv)); }); + cls.traitMethod(sigs::toNumberSet, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_NULL(); + }); cls.traitMethod(sigs::arithmeticAdd, operatorBinary); cls.traitMethod(sigs::arithmeticSubtract, operatorBinary); cls.traitMethod(sigs::arithmeticMultiply, operatorBinary); diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 2c321804598..6cc0907b09b 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -281,6 +281,39 @@ zv::Val pt_float_range_from_interval(double min, double max); /* FloatRangeType::createAll{Smaller,Greater}Than[OrEqualTo]($value) */ zv::Val pt_float_range_create_all(double value, bool greater, bool orEqual); +namespace phpstanturbo { + +/* the state of a PHPStan\Type\NumberSet (NumberSet.cpp): integer intervals + * with null for an unbounded side, closed float intervals, NAN */ +struct NumberSetData +{ + struct IntegerInterval + { + NullableLong min; + NullableLong max; + }; + struct FloatInterval + { + double min; + double max; + }; + + std::vector integerIntervals; + std::vector floatIntervals; + bool nan = false; +}; + +} // namespace phpstanturbo + +/* new NumberSet(...) from the data; UNDEF = pending exception */ +zv::Val pt_number_set_new(const phpstanturbo::NumberSetData &data); +/* the state of a NumberSet instance appended to `out`; false = pending + * exception */ +[[nodiscard]] bool pt_number_set_read(zend_object *numberSet, phpstanturbo::NumberSetData &out); +/* $type->toNumberSet() read into `out`, has = false for its null; false = + * pending exception */ +[[nodiscard]] bool pt_type_to_number_set(zval *type, phpstanturbo::NumberSetData &out, bool &has); + /* the bodies of IntegerType::looseCompare() and IntegerType::arithmeticPower() * (IntegerType.cpp), for the children's parent:: calls; UNDEF = pending * exception */ diff --git a/turbo-ext/src/UnionType.cpp b/turbo-ext/src/UnionType.cpp index c0121ec1628..6a0708b77cf 100644 --- a/turbo-ext/src/UnionType.cpp +++ b/turbo-ext/src/UnionType.cpp @@ -1601,6 +1601,21 @@ class UnionType } zv::Val toNumber() const { return unionTypes(UnionMemberOp::call(PT_LC("tonumber"))); } zv::Val toBitwiseNotType() const { return unionTypes(UnionMemberOp::call(PT_LC("tobitwisenottype"))); } + + /* the members' sets one after the other, null when a member has none; + * UNDEF = pending exception */ + zv::Val toNumberSet() const + { + zval *types = this->types(); + if (UNEXPECTED(types == NULL)) return zv::Val(); + NumberSetData data; + for (zv::ArrayEntry entry : zv::ArrRef(types)) { + bool has; + if (UNEXPECTED(!pt_type_to_number_set(entry.value().deref().raw(), data, has))) return zv::Val(); + if (!has) return zv::Val::null(); + } + return pt_number_set_new(data); + } zv::Val toGetClassResultType() const { return unionTypes(UnionMemberOp::call(PT_LC("togetclassresulttype"))); } zv::Val toClassConstantType(zval *reflectionProvider) const { return unionTypes(UnionMemberOp::call(PT_LC("toclassconstanttype"), 1, reflectionProvider)); } @@ -3400,6 +3415,9 @@ PT_MINIT_REGISTRATION(pt_register_union_type) cls.method(sigs::toNumber, [](INTERNAL_FUNCTION_PARAMETERS) { pt_ut_value0(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::toNumber); }); + cls.method(sigs::toNumberSet, [](INTERNAL_FUNCTION_PARAMETERS) { + pt_ut_value0(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::toNumberSet); + }); cls.method(sigs::toBitwiseNotType, [](INTERNAL_FUNCTION_PARAMETERS) { pt_ut_value0(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::toBitwiseNotType); }); diff --git a/turbo-ext/src/generated/ConstantFloatType.h b/turbo-ext/src/generated/ConstantFloatType.h index b1d14231b1a..63919008523 100644 --- a/turbo-ext/src/generated/ConstantFloatType.h +++ b/turbo-ext/src/generated/ConstantFloatType.h @@ -52,16 +52,18 @@ inline constexpr char strings[] = "PHPStan\\Type\\BooleanType\0" /* 175 */ "toBoolean\0" /* 200 */ "toIntegerValue\0" /* 210 */ - "toInteger\0" /* 225 */ - "toBitwiseNotType\0" /* 235 */ - "toAbsoluteNumber\0" /* 252 */ - "toArrayKey\0" /* 269 */ - "getFiniteTypes\0" /* 280 */ - "precision\0" /* 295 */ - "PHPStan\\Type\\GeneralizePrecision\0" /* 305 */ - "generalize\0" /* 338 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 349 */ - "toPhpDocNode"; /* 388 */ + "PHPStan\\Type\\NumberSet\0" /* 225 */ + "toNumberSet\0" /* 248 */ + "toInteger\0" /* 260 */ + "toBitwiseNotType\0" /* 270 */ + "toAbsoluteNumber\0" /* 287 */ + "toArrayKey\0" /* 304 */ + "getFiniteTypes\0" /* 315 */ + "precision\0" /* 330 */ + "PHPStan\\Type\\GeneralizePrecision\0" /* 340 */ + "generalize\0" /* 373 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 384 */ + "toPhpDocNode"; /* 423 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_DOUBLE), /* __construct $value */ reg::packed(18, MAY_BE_DOUBLE), /* getValue return */ @@ -76,14 +78,15 @@ inline constexpr reg::PackedArg args[] = { reg::packed(18, 0, 33), /* toString return */ reg::packed(18, 0, 175), /* toBoolean return */ reg::packed(18, MAY_BE_LONG), /* toIntegerValue return */ + reg::packed(18, 0, 225), /* toNumberSet return */ reg::packed(18, 0, 33), /* toInteger return */ reg::packed(18, 0, 33), /* toBitwiseNotType return */ reg::packed(18, 0, 33), /* toAbsoluteNumber return */ reg::packed(18, 0, 33), /* toArrayKey return */ reg::packed(18, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(295, 0, 305), /* generalize $precision */ + reg::packed(330, 0, 340), /* generalize $precision */ reg::packed(18, 0, 33), /* generalize return */ - reg::packed(18, 0, 349), /* toPhpDocNode return */ + reg::packed(18, 0, 384), /* toPhpDocNode return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -99,13 +102,14 @@ inline constexpr sigtab::Sig describe = { { 157 /* describe */, 1, 8, 1, 9, ZEND inline constexpr sigtab::Sig toString = { { 166 /* toString */, 0, 10, 0, 10, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toBoolean = { { 200 /* toBoolean */, 0, 11, 0, 11, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toIntegerValue = { { 210 /* toIntegerValue */, 0, 12, 0, 12, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toInteger = { { 225 /* toInteger */, 0, 13, 0, 13, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 235 /* toBitwiseNotType */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 252 /* toAbsoluteNumber */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArrayKey = { { 269 /* toArrayKey */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 280 /* getFiniteTypes */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig generalize = { { 338 /* generalize */, 1, 18, 1, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 388 /* toPhpDocNode */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 248 /* toNumberSet */, 0, 13, 0, 13, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 260 /* toInteger */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 270 /* toBitwiseNotType */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 287 /* toAbsoluteNumber */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 304 /* toArrayKey */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 315 /* getFiniteTypes */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalize = { { 373 /* generalize */, 1, 19, 1, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 423 /* toPhpDocNode */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ConstantFloatType diff --git a/turbo-ext/src/generated/ConstantIntegerType.h b/turbo-ext/src/generated/ConstantIntegerType.h index f8baf78c787..05e5650b3a3 100644 --- a/turbo-ext/src/generated/ConstantIntegerType.h +++ b/turbo-ext/src/generated/ConstantIntegerType.h @@ -39,43 +39,46 @@ inline constexpr char strings[] = "value\0" /* 0 */ "__construct\0" /* 6 */ "\0" /* 18 */ - "getValue\0" /* 19 */ - "type\0" /* 28 */ - "PHPStan\\Type\\Type\0" /* 33 */ - "PHPStan\\Type\\IsSuperTypeOfResult\0" /* 51 */ - "isSuperTypeOf\0" /* 84 */ - "level\0" /* 98 */ - "PHPStan\\Type\\VerbosityLevel\0" /* 104 */ - "describe\0" /* 132 */ - "toFloat\0" /* 141 */ - "toBitwiseNotType\0" /* 149 */ - "toAbsoluteNumber\0" /* 166 */ - "toString\0" /* 183 */ - "toArrayKey\0" /* 192 */ - "strictTypes\0" /* 203 */ - "toCoercedArgumentType\0" /* 215 */ - "precision\0" /* 237 */ - "PHPStan\\Type\\GeneralizePrecision\0" /* 247 */ - "generalize\0" /* 280 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 291 */ - "toPhpDocNode"; /* 330 */ + "PHPStan\\Type\\NumberSet\0" /* 19 */ + "toNumberSet\0" /* 42 */ + "getValue\0" /* 54 */ + "type\0" /* 63 */ + "PHPStan\\Type\\Type\0" /* 68 */ + "PHPStan\\Type\\IsSuperTypeOfResult\0" /* 86 */ + "isSuperTypeOf\0" /* 119 */ + "level\0" /* 133 */ + "PHPStan\\Type\\VerbosityLevel\0" /* 139 */ + "describe\0" /* 167 */ + "toFloat\0" /* 176 */ + "toBitwiseNotType\0" /* 184 */ + "toAbsoluteNumber\0" /* 201 */ + "toString\0" /* 218 */ + "toArrayKey\0" /* 227 */ + "strictTypes\0" /* 238 */ + "toCoercedArgumentType\0" /* 250 */ + "precision\0" /* 272 */ + "PHPStan\\Type\\GeneralizePrecision\0" /* 282 */ + "generalize\0" /* 315 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 326 */ + "toPhpDocNode"; /* 365 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_LONG), /* __construct $value */ + reg::packed(18, 0, 19), /* toNumberSet return */ reg::packed(18, MAY_BE_LONG), /* getValue return */ - reg::packed(28, 0, 33), /* isSuperTypeOf $type */ - reg::packed(18, 0, 51), /* isSuperTypeOf return */ - reg::packed(98, 0, 104), /* describe $level */ + reg::packed(63, 0, 68), /* isSuperTypeOf $type */ + reg::packed(18, 0, 86), /* isSuperTypeOf return */ + reg::packed(133, 0, 139), /* describe $level */ reg::packed(18, MAY_BE_STRING), /* describe return */ - reg::packed(18, 0, 33), /* toFloat return */ - reg::packed(18, 0, 33), /* toBitwiseNotType return */ - reg::packed(18, 0, 33), /* toAbsoluteNumber return */ - reg::packed(18, 0, 33), /* toString return */ - reg::packed(18, 0, 33), /* toArrayKey return */ - reg::packed(203, MAY_BE_BOOL), /* toCoercedArgumentType $strictTypes */ - reg::packed(18, 0, 33), /* toCoercedArgumentType return */ - reg::packed(237, 0, 247), /* generalize $precision */ - reg::packed(18, 0, 33), /* generalize return */ - reg::packed(18, 0, 291), /* toPhpDocNode return */ + reg::packed(18, 0, 68), /* toFloat return */ + reg::packed(18, 0, 68), /* toBitwiseNotType return */ + reg::packed(18, 0, 68), /* toAbsoluteNumber return */ + reg::packed(18, 0, 68), /* toString return */ + reg::packed(18, 0, 68), /* toArrayKey return */ + reg::packed(238, MAY_BE_BOOL), /* toCoercedArgumentType $strictTypes */ + reg::packed(18, 0, 68), /* toCoercedArgumentType return */ + reg::packed(272, 0, 282), /* generalize $precision */ + reg::packed(18, 0, 68), /* generalize return */ + reg::packed(18, 0, 326), /* toPhpDocNode return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -83,17 +86,18 @@ using Sig = reg::Sig; /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { inline constexpr sigtab::Sig __construct = { { 6 /* __construct */, 1, 0, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getValue = { { 19 /* getValue */, 0, 1, 0, 1, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSuperTypeOf = { { 84 /* isSuperTypeOf */, 1, 2, 1, 3, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig describe = { { 132 /* describe */, 1, 4, 1, 5, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toFloat = { { 141 /* toFloat */, 0, 6, 0, 6, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 149 /* toBitwiseNotType */, 0, 7, 0, 7, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 166 /* toAbsoluteNumber */, 0, 8, 0, 8, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 183 /* toString */, 0, 9, 0, 9, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArrayKey = { { 192 /* toArrayKey */, 0, 10, 0, 10, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toCoercedArgumentType = { { 215 /* toCoercedArgumentType */, 1, 11, 1, 12, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig generalize = { { 280 /* generalize */, 1, 13, 1, 14, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 330 /* toPhpDocNode */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 42 /* toNumberSet */, 0, 1, 0, 1, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getValue = { { 54 /* getValue */, 0, 2, 0, 2, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSuperTypeOf = { { 119 /* isSuperTypeOf */, 1, 3, 1, 4, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig describe = { { 167 /* describe */, 1, 5, 1, 6, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 176 /* toFloat */, 0, 7, 0, 7, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 184 /* toBitwiseNotType */, 0, 8, 0, 8, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 201 /* toAbsoluteNumber */, 0, 9, 0, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 218 /* toString */, 0, 10, 0, 10, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 227 /* toArrayKey */, 0, 11, 0, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toCoercedArgumentType = { { 250 /* toCoercedArgumentType */, 1, 12, 1, 13, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalize = { { 315 /* generalize */, 1, 14, 1, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 365 /* toPhpDocNode */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ConstantIntegerType diff --git a/turbo-ext/src/generated/FloatRangeType.h b/turbo-ext/src/generated/FloatRangeType.h index 0c79a5e6946..ce4dc239ecc 100644 --- a/turbo-ext/src/generated/FloatRangeType.h +++ b/turbo-ext/src/generated/FloatRangeType.h @@ -82,15 +82,17 @@ inline constexpr char strings[] = "excludesZero\0" /* 731 */ "PHPStan\\Type\\BooleanType\0" /* 744 */ "toBoolean\0" /* 769 */ - "toInteger\0" /* 779 */ - "toString\0" /* 789 */ - "toAbsoluteNumber\0" /* 798 */ - "tryUnion\0" /* 815 */ - "tryIntersect\0" /* 824 */ - "typeToRemove\0" /* 837 */ - "tryRemove\0" /* 850 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 860 */ - "toPhpDocNode"; /* 899 */ + "PHPStan\\Type\\NumberSet\0" /* 779 */ + "toNumberSet\0" /* 802 */ + "toInteger\0" /* 814 */ + "toString\0" /* 824 */ + "toAbsoluteNumber\0" /* 833 */ + "tryUnion\0" /* 850 */ + "tryIntersect\0" /* 859 */ + "typeToRemove\0" /* 872 */ + "tryRemove\0" /* 885 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 895 */ + "toPhpDocNode"; /* 934 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_DOUBLE), /* __construct $min */ reg::packed(4, MAY_BE_DOUBLE), /* __construct $max */ @@ -159,6 +161,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(20, 0, 21), /* getGreaterOrEqualType return */ reg::packed(20, MAY_BE_BOOL), /* excludesZero return */ reg::packed(20, 0, 744), /* toBoolean return */ + reg::packed(20, 0, 779), /* toNumberSet return */ reg::packed(20, 0, 21), /* toInteger return */ reg::packed(20, 0, 21), /* toString return */ reg::packed(20, 0, 21), /* toAbsoluteNumber return */ @@ -166,9 +169,9 @@ inline constexpr reg::PackedArg args[] = { reg::packed(20, MAY_BE_NULL, 21), /* tryUnion return */ reg::packed(385, 0, 21), /* tryIntersect $otherType */ reg::packed(20, MAY_BE_NULL, 21), /* tryIntersect return */ - reg::packed(837, 0, 21), /* tryRemove $typeToRemove */ + reg::packed(872, 0, 21), /* tryRemove $typeToRemove */ reg::packed(20, MAY_BE_NULL, 21), /* tryRemove return */ - reg::packed(20, 0, 860), /* toPhpDocNode return */ + reg::packed(20, 0, 895), /* toPhpDocNode return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -205,13 +208,14 @@ inline constexpr sigtab::Sig getGreaterType = { { 694 /* getGreaterType */, 1, 6 inline constexpr sigtab::Sig getGreaterOrEqualType = { { 709 /* getGreaterOrEqualType */, 1, 63, 1, 64, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig excludesZero = { { 731 /* excludesZero */, 0, 65, 0, 65, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig toBoolean = { { 769 /* toBoolean */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toInteger = { { 779 /* toInteger */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 789 /* toString */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 798 /* toAbsoluteNumber */, 0, 69, 0, 69, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryUnion = { { 815 /* tryUnion */, 1, 70, 1, 71, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryIntersect = { { 824 /* tryIntersect */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryRemove = { { 850 /* tryRemove */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 899 /* toPhpDocNode */, 0, 76, 0, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 802 /* toNumberSet */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 814 /* toInteger */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 824 /* toString */, 0, 69, 0, 69, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 833 /* toAbsoluteNumber */, 0, 70, 0, 70, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryUnion = { { 850 /* tryUnion */, 1, 71, 1, 72, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryIntersect = { { 859 /* tryIntersect */, 1, 73, 1, 74, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryRemove = { { 885 /* tryRemove */, 1, 75, 1, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 934 /* toPhpDocNode */, 0, 77, 0, 77, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::FloatRangeType diff --git a/turbo-ext/src/generated/FloatType.h b/turbo-ext/src/generated/FloatType.h index 83007e6d45d..96bd0424125 100644 --- a/turbo-ext/src/generated/FloatType.h +++ b/turbo-ext/src/generated/FloatType.h @@ -57,56 +57,58 @@ inline constexpr char strings[] = "PHPStan\\Type\\VerbosityLevel\0" /* 252 */ "describe\0" /* 280 */ "toNumber\0" /* 289 */ - "toBitwiseNotType\0" /* 298 */ - "toAbsoluteNumber\0" /* 315 */ - "toFloat\0" /* 332 */ - "toInteger\0" /* 340 */ - "toString\0" /* 350 */ - "toArray\0" /* 359 */ - "toArrayKey\0" /* 367 */ - "toCoercedArgumentType\0" /* 378 */ - "PHPStan\\TrinaryLogic\0" /* 400 */ - "isOffsetAccessLegal\0" /* 421 */ - "isNull\0" /* 441 */ - "isConstantValue\0" /* 448 */ - "isConstantScalarValue\0" /* 464 */ - "getConstantScalarTypes\0" /* 486 */ - "getConstantScalarValues\0" /* 509 */ - "isTrue\0" /* 533 */ - "isFalse\0" /* 540 */ - "isBoolean\0" /* 548 */ - "isFloat\0" /* 558 */ - "isInteger\0" /* 566 */ - "isString\0" /* 576 */ - "isNumericString\0" /* 585 */ - "isDecimalIntegerString\0" /* 601 */ - "isNonEmptyString\0" /* 624 */ - "isNonFalsyString\0" /* 641 */ - "isLiteralString\0" /* 658 */ - "isLowercaseString\0" /* 674 */ - "isClassString\0" /* 692 */ - "isUppercaseString\0" /* 706 */ - "getClassStringObjectType\0" /* 724 */ - "getObjectTypeOrClassStringObjectType\0" /* 749 */ - "isVoid\0" /* 786 */ - "isScalar\0" /* 793 */ - "phpVersion\0" /* 802 */ - "PHPStan\\Php\\PhpVersion\0" /* 813 */ - "PHPStan\\Type\\BooleanType\0" /* 836 */ - "looseCompare\0" /* 861 */ - "cb\0" /* 874 */ - "traverse\0" /* 877 */ - "right\0" /* 886 */ - "traverseSimultaneously\0" /* 892 */ - "exponent\0" /* 915 */ - "arithmeticPower\0" /* 924 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 940 */ - "toPhpDocNode\0" /* 979 */ - "position\0" /* 992 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1001 */ - "toNativePhpTypeNode\0" /* 1033 */ - "getFiniteTypes\0" /* 1053 */ - "hasTemplateOrLateResolvableType"; /* 1068 */ + "PHPStan\\Type\\NumberSet\0" /* 298 */ + "toNumberSet\0" /* 321 */ + "toBitwiseNotType\0" /* 333 */ + "toAbsoluteNumber\0" /* 350 */ + "toFloat\0" /* 367 */ + "toInteger\0" /* 375 */ + "toString\0" /* 385 */ + "toArray\0" /* 394 */ + "toArrayKey\0" /* 402 */ + "toCoercedArgumentType\0" /* 413 */ + "PHPStan\\TrinaryLogic\0" /* 435 */ + "isOffsetAccessLegal\0" /* 456 */ + "isNull\0" /* 476 */ + "isConstantValue\0" /* 483 */ + "isConstantScalarValue\0" /* 499 */ + "getConstantScalarTypes\0" /* 521 */ + "getConstantScalarValues\0" /* 544 */ + "isTrue\0" /* 568 */ + "isFalse\0" /* 575 */ + "isBoolean\0" /* 583 */ + "isFloat\0" /* 593 */ + "isInteger\0" /* 601 */ + "isString\0" /* 611 */ + "isNumericString\0" /* 620 */ + "isDecimalIntegerString\0" /* 636 */ + "isNonEmptyString\0" /* 659 */ + "isNonFalsyString\0" /* 676 */ + "isLiteralString\0" /* 693 */ + "isLowercaseString\0" /* 709 */ + "isClassString\0" /* 727 */ + "isUppercaseString\0" /* 741 */ + "getClassStringObjectType\0" /* 759 */ + "getObjectTypeOrClassStringObjectType\0" /* 784 */ + "isVoid\0" /* 821 */ + "isScalar\0" /* 828 */ + "phpVersion\0" /* 837 */ + "PHPStan\\Php\\PhpVersion\0" /* 848 */ + "PHPStan\\Type\\BooleanType\0" /* 871 */ + "looseCompare\0" /* 896 */ + "cb\0" /* 909 */ + "traverse\0" /* 912 */ + "right\0" /* 921 */ + "traverseSimultaneously\0" /* 927 */ + "exponent\0" /* 950 */ + "arithmeticPower\0" /* 959 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 975 */ + "toPhpDocNode\0" /* 1014 */ + "position\0" /* 1027 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1036 */ + "toNativePhpTypeNode\0" /* 1068 */ + "getFiniteTypes\0" /* 1088 */ + "hasTemplateOrLateResolvableType"; /* 1103 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -124,6 +126,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(246, 0, 252), /* describe $level */ reg::packed(12, MAY_BE_STRING), /* describe return */ reg::packed(12, 0, 104), /* toNumber return */ + reg::packed(12, 0, 298), /* toNumberSet return */ reg::packed(12, 0, 104), /* toBitwiseNotType return */ reg::packed(12, 0, 104), /* toAbsoluteNumber return */ reg::packed(12, 0, 104), /* toFloat return */ @@ -133,43 +136,43 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* toArrayKey return */ reg::packed(122, MAY_BE_BOOL), /* toCoercedArgumentType $strictTypes */ reg::packed(12, 0, 104), /* toCoercedArgumentType return */ - reg::packed(12, 0, 400), /* isOffsetAccessLegal return */ - reg::packed(12, 0, 400), /* isNull return */ - reg::packed(12, 0, 400), /* isConstantValue return */ - reg::packed(12, 0, 400), /* isConstantScalarValue return */ + reg::packed(12, 0, 435), /* isOffsetAccessLegal return */ + reg::packed(12, 0, 435), /* isNull return */ + reg::packed(12, 0, 435), /* isConstantValue return */ + reg::packed(12, 0, 435), /* isConstantScalarValue return */ reg::packed(12, MAY_BE_ARRAY), /* getConstantScalarTypes return */ reg::packed(12, MAY_BE_ARRAY), /* getConstantScalarValues return */ - reg::packed(12, 0, 400), /* isTrue return */ - reg::packed(12, 0, 400), /* isFalse return */ - reg::packed(12, 0, 400), /* isBoolean return */ - reg::packed(12, 0, 400), /* isFloat return */ - reg::packed(12, 0, 400), /* isInteger return */ - reg::packed(12, 0, 400), /* isString return */ - reg::packed(12, 0, 400), /* isNumericString return */ - reg::packed(12, 0, 400), /* isDecimalIntegerString return */ - reg::packed(12, 0, 400), /* isNonEmptyString return */ - reg::packed(12, 0, 400), /* isNonFalsyString return */ - reg::packed(12, 0, 400), /* isLiteralString return */ - reg::packed(12, 0, 400), /* isLowercaseString return */ - reg::packed(12, 0, 400), /* isClassString return */ - reg::packed(12, 0, 400), /* isUppercaseString return */ + reg::packed(12, 0, 435), /* isTrue return */ + reg::packed(12, 0, 435), /* isFalse return */ + reg::packed(12, 0, 435), /* isBoolean return */ + reg::packed(12, 0, 435), /* isFloat return */ + reg::packed(12, 0, 435), /* isInteger return */ + reg::packed(12, 0, 435), /* isString return */ + reg::packed(12, 0, 435), /* isNumericString return */ + reg::packed(12, 0, 435), /* isDecimalIntegerString return */ + reg::packed(12, 0, 435), /* isNonEmptyString return */ + reg::packed(12, 0, 435), /* isNonFalsyString return */ + reg::packed(12, 0, 435), /* isLiteralString return */ + reg::packed(12, 0, 435), /* isLowercaseString return */ + reg::packed(12, 0, 435), /* isClassString return */ + reg::packed(12, 0, 435), /* isUppercaseString return */ reg::packed(12, 0, 104), /* getClassStringObjectType return */ reg::packed(12, 0, 104), /* getObjectTypeOrClassStringObjectType return */ - reg::packed(12, 0, 400), /* isVoid return */ - reg::packed(12, 0, 400), /* isScalar return */ + reg::packed(12, 0, 435), /* isVoid return */ + reg::packed(12, 0, 435), /* isScalar return */ reg::packed(99, 0, 104), /* looseCompare $type */ - reg::packed(802, 0, 813), /* looseCompare $phpVersion */ - reg::packed(12, 0, 836), /* looseCompare return */ - reg::packed(874, MAY_BE_CALLABLE), /* traverse $cb */ + reg::packed(837, 0, 848), /* looseCompare $phpVersion */ + reg::packed(12, 0, 871), /* looseCompare return */ + reg::packed(909, MAY_BE_CALLABLE), /* traverse $cb */ reg::packed(12, 0, 104), /* traverse return */ - reg::packed(886, 0, 104), /* traverseSimultaneously $right */ - reg::packed(874, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ + reg::packed(921, 0, 104), /* traverseSimultaneously $right */ + reg::packed(909, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(12, 0, 104), /* traverseSimultaneously return */ - reg::packed(915, 0, 104), /* arithmeticPower $exponent */ - reg::packed(802, 0, 813), /* arithmeticPower $phpVersion */ + reg::packed(950, 0, 104), /* arithmeticPower $exponent */ + reg::packed(837, 0, 848), /* arithmeticPower $phpVersion */ reg::packed(12, 0, 104), /* arithmeticPower return */ - reg::packed(12, 0, 940), /* toPhpDocNode return */ - reg::packed(992, 0, 1001), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 975), /* toPhpDocNode return */ + reg::packed(1027, 0, 1036), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; @@ -189,46 +192,47 @@ inline constexpr sigtab::Sig tryRemove = { { 229 /* tryRemove */, 1, 9, 1, 10, Z inline constexpr sigtab::Sig equals = { { 239 /* equals */, 1, 11, 1, 12, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig describe = { { 280 /* describe */, 1, 13, 1, 14, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toNumber = { { 289 /* toNumber */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 298 /* toBitwiseNotType */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 315 /* toAbsoluteNumber */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toFloat = { { 332 /* toFloat */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toInteger = { { 340 /* toInteger */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 350 /* toString */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArray = { { 359 /* toArray */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArrayKey = { { 367 /* toArrayKey */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toCoercedArgumentType = { { 378 /* toCoercedArgumentType */, 1, 23, 1, 24, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isOffsetAccessLegal = { { 421 /* isOffsetAccessLegal */, 0, 25, 0, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNull = { { 441 /* isNull */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isConstantValue = { { 448 /* isConstantValue */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isConstantScalarValue = { { 464 /* isConstantScalarValue */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantScalarTypes = { { 486 /* getConstantScalarTypes */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantScalarValues = { { 509 /* getConstantScalarValues */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isTrue = { { 533 /* isTrue */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isFalse = { { 540 /* isFalse */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isBoolean = { { 548 /* isBoolean */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isFloat = { { 558 /* isFloat */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInteger = { { 566 /* isInteger */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isString = { { 576 /* isString */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNumericString = { { 585 /* isNumericString */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isDecimalIntegerString = { { 601 /* isDecimalIntegerString */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNonEmptyString = { { 624 /* isNonEmptyString */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNonFalsyString = { { 641 /* isNonFalsyString */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isLiteralString = { { 658 /* isLiteralString */, 0, 41, 0, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isLowercaseString = { { 674 /* isLowercaseString */, 0, 42, 0, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isClassString = { { 692 /* isClassString */, 0, 43, 0, 43, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isUppercaseString = { { 706 /* isUppercaseString */, 0, 44, 0, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getClassStringObjectType = { { 724 /* getClassStringObjectType */, 0, 45, 0, 45, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getObjectTypeOrClassStringObjectType = { { 749 /* getObjectTypeOrClassStringObjectType */, 0, 46, 0, 46, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isVoid = { { 786 /* isVoid */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isScalar = { { 793 /* isScalar */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 861 /* looseCompare */, 2, 49, 2, 51, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig traverse = { { 877 /* traverse */, 1, 52, 1, 53, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig traverseSimultaneously = { { 892 /* traverseSimultaneously */, 2, 54, 2, 56, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticPower = { { 924 /* arithmeticPower */, 2, 57, 2, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 979 /* toPhpDocNode */, 0, 60, 0, 60, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1033 /* toNativePhpTypeNode */, 1, 61, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1053 /* getFiniteTypes */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1068 /* hasTemplateOrLateResolvableType */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 321 /* toNumberSet */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 333 /* toBitwiseNotType */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 350 /* toAbsoluteNumber */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 367 /* toFloat */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 375 /* toInteger */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 385 /* toString */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArray = { { 394 /* toArray */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 402 /* toArrayKey */, 0, 23, 0, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toCoercedArgumentType = { { 413 /* toCoercedArgumentType */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isOffsetAccessLegal = { { 456 /* isOffsetAccessLegal */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNull = { { 476 /* isNull */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConstantValue = { { 483 /* isConstantValue */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConstantScalarValue = { { 499 /* isConstantScalarValue */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantScalarTypes = { { 521 /* getConstantScalarTypes */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantScalarValues = { { 544 /* getConstantScalarValues */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isTrue = { { 568 /* isTrue */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFalse = { { 575 /* isFalse */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isBoolean = { { 583 /* isBoolean */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFloat = { { 593 /* isFloat */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInteger = { { 601 /* isInteger */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isString = { { 611 /* isString */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNumericString = { { 620 /* isNumericString */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isDecimalIntegerString = { { 636 /* isDecimalIntegerString */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNonEmptyString = { { 659 /* isNonEmptyString */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNonFalsyString = { { 676 /* isNonFalsyString */, 0, 41, 0, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isLiteralString = { { 693 /* isLiteralString */, 0, 42, 0, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isLowercaseString = { { 709 /* isLowercaseString */, 0, 43, 0, 43, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isClassString = { { 727 /* isClassString */, 0, 44, 0, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUppercaseString = { { 741 /* isUppercaseString */, 0, 45, 0, 45, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClassStringObjectType = { { 759 /* getClassStringObjectType */, 0, 46, 0, 46, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getObjectTypeOrClassStringObjectType = { { 784 /* getObjectTypeOrClassStringObjectType */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isVoid = { { 821 /* isVoid */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isScalar = { { 828 /* isScalar */, 0, 49, 0, 49, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 896 /* looseCompare */, 2, 50, 2, 52, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig traverse = { { 912 /* traverse */, 1, 53, 1, 54, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig traverseSimultaneously = { { 927 /* traverseSimultaneously */, 2, 55, 2, 57, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 959 /* arithmeticPower */, 2, 58, 2, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1014 /* toPhpDocNode */, 0, 61, 0, 61, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1068 /* toNativePhpTypeNode */, 1, 62, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1088 /* getFiniteTypes */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1103 /* hasTemplateOrLateResolvableType */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::FloatType diff --git a/turbo-ext/src/generated/IntegerRangeType.h b/turbo-ext/src/generated/IntegerRangeType.h index 5a9019a9ed5..dae62e6b512 100644 --- a/turbo-ext/src/generated/IntegerRangeType.h +++ b/turbo-ext/src/generated/IntegerRangeType.h @@ -56,55 +56,57 @@ 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 */ - "compare\0" /* 671 */ - "compareEachInteger\0" /* 679 */ - "getSmallerType\0" /* 698 */ - "getSmallerOrEqualType\0" /* 713 */ - "getGreaterType\0" /* 735 */ - "getGreaterOrEqualType\0" /* 750 */ - "toFloat\0" /* 772 */ - "PHPStan\\Type\\BooleanType\0" /* 780 */ - "toBoolean\0" /* 805 */ - "toAbsoluteNumber\0" /* 815 */ - "toBitwiseNotType\0" /* 832 */ - "toString\0" /* 849 */ - "tryUnion\0" /* 858 */ - "tryIntersect\0" /* 867 */ - "typeToRemove\0" /* 880 */ - "tryRemove\0" /* 893 */ - "getFiniteTypes\0" /* 903 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 918 */ - "toPhpDocNode\0" /* 957 */ - "looseCompare"; /* 970 */ + "PHPStan\\Type\\NumberSet\0" /* 223 */ + "toNumberSet\0" /* 246 */ + "getMin\0" /* 258 */ + "getMax\0" /* 265 */ + "level\0" /* 272 */ + "PHPStan\\Type\\VerbosityLevel\0" /* 278 */ + "describe\0" /* 306 */ + "amount\0" /* 315 */ + "type\0" /* 322 */ + "strictTypes\0" /* 327 */ + "PHPStan\\Type\\AcceptsResult\0" /* 339 */ + "accepts\0" /* 366 */ + "PHPStan\\Type\\IsSuperTypeOfResult\0" /* 374 */ + "isSuperTypeOf\0" /* 407 */ + "otherType\0" /* 421 */ + "isSubTypeOf\0" /* 431 */ + "PHPStan\\Type\\UnionType\0" /* 443 */ + "isSubTypeOfUnionWithReason\0" /* 466 */ + "acceptingType\0" /* 493 */ + "isAcceptedBy\0" /* 507 */ + "equals\0" /* 520 */ + "precision\0" /* 527 */ + "PHPStan\\Type\\GeneralizePrecision\0" /* 537 */ + "generalize\0" /* 570 */ + "phpVersion\0" /* 581 */ + "PHPStan\\Php\\PhpVersion\0" /* 592 */ + "PHPStan\\TrinaryLogic\0" /* 615 */ + "isSmallerThan\0" /* 636 */ + "isSmallerThanOrEqual\0" /* 650 */ + "isGreaterThan\0" /* 671 */ + "isGreaterThanOrEqual\0" /* 685 */ + "compare\0" /* 706 */ + "compareEachInteger\0" /* 714 */ + "getSmallerType\0" /* 733 */ + "getSmallerOrEqualType\0" /* 748 */ + "getGreaterType\0" /* 770 */ + "getGreaterOrEqualType\0" /* 785 */ + "toFloat\0" /* 807 */ + "PHPStan\\Type\\BooleanType\0" /* 815 */ + "toBoolean\0" /* 840 */ + "toAbsoluteNumber\0" /* 850 */ + "toBitwiseNotType\0" /* 867 */ + "toString\0" /* 884 */ + "tryUnion\0" /* 893 */ + "tryIntersect\0" /* 902 */ + "typeToRemove\0" /* 915 */ + "tryRemove\0" /* 928 */ + "getFiniteTypes\0" /* 938 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 953 */ + "toPhpDocNode\0" /* 992 */ + "looseCompare"; /* 1005 */ 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 */ @@ -126,68 +128,69 @@ 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(28, 0, 223), /* toNumberSet 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(272, 0, 278), /* describe $level */ reg::packed(28, MAY_BE_STRING), /* describe return */ - reg::packed(280, MAY_BE_LONG), /* shift $amount */ + reg::packed(315, 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(322, 0, 29), /* accepts $type */ + reg::packed(327, MAY_BE_BOOL), /* accepts $strictTypes */ + reg::packed(28, 0, 339), /* accepts return */ + reg::packed(322, 0, 29), /* isSuperTypeOf $type */ + reg::packed(28, 0, 374), /* isSuperTypeOf return */ + reg::packed(421, 0, 29), /* isSubTypeOf $otherType */ + reg::packed(28, 0, 374), /* isSubTypeOf return */ + reg::packed(421, 0, 443), /* isSubTypeOfUnionWithReason $otherType */ + reg::packed(28, 0, 374), /* isSubTypeOfUnionWithReason return */ + reg::packed(493, 0, 29), /* isAcceptedBy $acceptingType */ + reg::packed(327, MAY_BE_BOOL), /* isAcceptedBy $strictTypes */ + reg::packed(28, 0, 339), /* isAcceptedBy return */ + reg::packed(322, 0, 29), /* equals $type */ reg::packed(28, MAY_BE_BOOL), /* equals return */ - reg::packed(492, 0, 502), /* generalize $precision */ + reg::packed(527, 0, 537), /* 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(386, 0, 29), /* compareEachInteger $otherType */ - reg::packed(546, 0, 557), /* compareEachInteger $phpVersion */ - reg::packed(671, MAY_BE_CALLABLE), /* compareEachInteger $compare */ - reg::packed(28, MAY_BE_NULL, 580), /* compareEachInteger return */ - reg::packed(546, 0, 557), /* getSmallerType $phpVersion */ + reg::packed(421, 0, 29), /* isSmallerThan $otherType */ + reg::packed(581, 0, 592), /* isSmallerThan $phpVersion */ + reg::packed(28, 0, 615), /* isSmallerThan return */ + reg::packed(421, 0, 29), /* isSmallerThanOrEqual $otherType */ + reg::packed(581, 0, 592), /* isSmallerThanOrEqual $phpVersion */ + reg::packed(28, 0, 615), /* isSmallerThanOrEqual return */ + reg::packed(421, 0, 29), /* isGreaterThan $otherType */ + reg::packed(581, 0, 592), /* isGreaterThan $phpVersion */ + reg::packed(28, 0, 615), /* isGreaterThan return */ + reg::packed(421, 0, 29), /* isGreaterThanOrEqual $otherType */ + reg::packed(581, 0, 592), /* isGreaterThanOrEqual $phpVersion */ + reg::packed(28, 0, 615), /* isGreaterThanOrEqual return */ + reg::packed(421, 0, 29), /* compareEachInteger $otherType */ + reg::packed(581, 0, 592), /* compareEachInteger $phpVersion */ + reg::packed(706, MAY_BE_CALLABLE), /* compareEachInteger $compare */ + reg::packed(28, MAY_BE_NULL, 615), /* compareEachInteger return */ + reg::packed(581, 0, 592), /* getSmallerType $phpVersion */ reg::packed(28, 0, 29), /* getSmallerType return */ - reg::packed(546, 0, 557), /* getSmallerOrEqualType $phpVersion */ + reg::packed(581, 0, 592), /* getSmallerOrEqualType $phpVersion */ reg::packed(28, 0, 29), /* getSmallerOrEqualType return */ - reg::packed(546, 0, 557), /* getGreaterType $phpVersion */ + reg::packed(581, 0, 592), /* getGreaterType $phpVersion */ reg::packed(28, 0, 29), /* getGreaterType return */ - reg::packed(546, 0, 557), /* getGreaterOrEqualType $phpVersion */ + reg::packed(581, 0, 592), /* getGreaterOrEqualType $phpVersion */ reg::packed(28, 0, 29), /* getGreaterOrEqualType return */ reg::packed(28, 0, 29), /* toFloat return */ - reg::packed(28, 0, 780), /* toBoolean return */ + reg::packed(28, 0, 815), /* 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(421, 0, 29), /* tryUnion $otherType */ reg::packed(28, MAY_BE_NULL, 29), /* tryUnion return */ - reg::packed(386, 0, 29), /* tryIntersect $otherType */ + reg::packed(421, 0, 29), /* tryIntersect $otherType */ reg::packed(28, MAY_BE_NULL, 29), /* tryIntersect return */ - reg::packed(880, 0, 29), /* tryRemove $typeToRemove */ + reg::packed(915, 0, 29), /* tryRemove $typeToRemove */ reg::packed(28, MAY_BE_NULL, 29), /* tryRemove return */ reg::packed(28, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(28, 0, 918), /* toPhpDocNode return */ - reg::packed(287, 0, 29), /* looseCompare $type */ - reg::packed(546, 0, 557), /* looseCompare $phpVersion */ - reg::packed(28, 0, 780), /* looseCompare return */ + reg::packed(28, 0, 953), /* toPhpDocNode return */ + reg::packed(322, 0, 29), /* looseCompare $type */ + reg::packed(581, 0, 592), /* looseCompare $phpVersion */ + reg::packed(28, 0, 815), /* looseCompare return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -201,37 +204,38 @@ 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 compareEachInteger = { { 679 /* compareEachInteger */, 3, 54, 3, 57, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getSmallerType = { { 698 /* getSmallerType */, 1, 58, 1, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerOrEqualType = { { 713 /* getSmallerOrEqualType */, 1, 60, 1, 61, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterType = { { 735 /* getGreaterType */, 1, 62, 1, 63, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterOrEqualType = { { 750 /* getGreaterOrEqualType */, 1, 64, 1, 65, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toFloat = { { 772 /* toFloat */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBoolean = { { 805 /* toBoolean */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 815 /* toAbsoluteNumber */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 832 /* toBitwiseNotType */, 0, 69, 0, 69, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 849 /* toString */, 0, 70, 0, 70, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryUnion = { { 858 /* tryUnion */, 1, 71, 1, 72, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryIntersect = { { 867 /* tryIntersect */, 1, 73, 1, 74, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryRemove = { { 893 /* tryRemove */, 1, 75, 1, 76, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 903 /* getFiniteTypes */, 0, 77, 0, 77, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 957 /* toPhpDocNode */, 0, 78, 0, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 970 /* looseCompare */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 246 /* toNumberSet */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMin = { { 258 /* getMin */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMax = { { 265 /* getMax */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig describe = { { 306 /* describe */, 1, 23, 1, 24, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shift = { { 20 /* shift */, 1, 25, 1, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig accepts = { { 366 /* accepts */, 2, 27, 2, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSuperTypeOf = { { 407 /* isSuperTypeOf */, 1, 30, 1, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSubTypeOf = { { 431 /* isSubTypeOf */, 1, 32, 1, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSubTypeOfUnionWithReason = { { 466 /* isSubTypeOfUnionWithReason */, 1, 34, 1, 35, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isAcceptedBy = { { 507 /* isAcceptedBy */, 2, 36, 2, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig equals = { { 520 /* equals */, 1, 39, 1, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalize = { { 570 /* generalize */, 1, 41, 1, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThan = { { 636 /* isSmallerThan */, 2, 43, 2, 45, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 650 /* isSmallerThanOrEqual */, 2, 46, 2, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isGreaterThan = { { 671 /* isGreaterThan */, 2, 49, 2, 51, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isGreaterThanOrEqual = { { 685 /* isGreaterThanOrEqual */, 2, 52, 2, 54, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig compareEachInteger = { { 714 /* compareEachInteger */, 3, 55, 3, 58, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getSmallerType = { { 733 /* getSmallerType */, 1, 59, 1, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerOrEqualType = { { 748 /* getSmallerOrEqualType */, 1, 61, 1, 62, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterType = { { 770 /* getGreaterType */, 1, 63, 1, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterOrEqualType = { { 785 /* getGreaterOrEqualType */, 1, 65, 1, 66, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 807 /* toFloat */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBoolean = { { 840 /* toBoolean */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 850 /* toAbsoluteNumber */, 0, 69, 0, 69, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 867 /* toBitwiseNotType */, 0, 70, 0, 70, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 884 /* toString */, 0, 71, 0, 71, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryUnion = { { 893 /* tryUnion */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryIntersect = { { 902 /* tryIntersect */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryRemove = { { 928 /* tryRemove */, 1, 76, 1, 77, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 938 /* getFiniteTypes */, 0, 78, 0, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 992 /* toPhpDocNode */, 0, 79, 0, 79, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 1005 /* looseCompare */, 2, 80, 2, 82, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntegerRangeType diff --git a/turbo-ext/src/generated/IntegerType.h b/turbo-ext/src/generated/IntegerType.h index 7233a0b2136..3cc3dc3a679 100644 --- a/turbo-ext/src/generated/IntegerType.h +++ b/turbo-ext/src/generated/IntegerType.h @@ -46,45 +46,48 @@ inline constexpr char strings[] = "getConstantStrings\0" /* 56 */ "PHPStan\\Type\\Type\0" /* 75 */ "toNumber\0" /* 93 */ - "toBitwiseNotType\0" /* 102 */ - "toAbsoluteNumber\0" /* 119 */ - "toFloat\0" /* 136 */ - "toInteger\0" /* 144 */ - "toString\0" /* 154 */ - "toArray\0" /* 163 */ - "toArrayKey\0" /* 171 */ - "strictTypes\0" /* 182 */ - "toCoercedArgumentType\0" /* 194 */ - "PHPStan\\TrinaryLogic\0" /* 216 */ - "isOffsetAccessLegal\0" /* 237 */ - "isNull\0" /* 257 */ - "isTrue\0" /* 264 */ - "isFalse\0" /* 271 */ - "isBoolean\0" /* 279 */ - "isFloat\0" /* 289 */ - "isInteger\0" /* 297 */ - "isScalar\0" /* 307 */ - "type\0" /* 316 */ - "phpVersion\0" /* 321 */ - "PHPStan\\Php\\PhpVersion\0" /* 332 */ - "PHPStan\\Type\\BooleanType\0" /* 355 */ - "looseCompare\0" /* 380 */ - "typeToRemove\0" /* 393 */ - "tryRemove\0" /* 406 */ - "getFiniteTypes\0" /* 416 */ - "exponent\0" /* 431 */ - "arithmeticPower\0" /* 440 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 456 */ - "toPhpDocNode\0" /* 495 */ - "position\0" /* 508 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 517 */ - "toNativePhpTypeNode\0" /* 549 */ - "hasTemplateOrLateResolvableType"; /* 569 */ + "PHPStan\\Type\\NumberSet\0" /* 102 */ + "toNumberSet\0" /* 125 */ + "toBitwiseNotType\0" /* 137 */ + "toAbsoluteNumber\0" /* 154 */ + "toFloat\0" /* 171 */ + "toInteger\0" /* 179 */ + "toString\0" /* 189 */ + "toArray\0" /* 198 */ + "toArrayKey\0" /* 206 */ + "strictTypes\0" /* 217 */ + "toCoercedArgumentType\0" /* 229 */ + "PHPStan\\TrinaryLogic\0" /* 251 */ + "isOffsetAccessLegal\0" /* 272 */ + "isNull\0" /* 292 */ + "isTrue\0" /* 299 */ + "isFalse\0" /* 306 */ + "isBoolean\0" /* 314 */ + "isFloat\0" /* 324 */ + "isInteger\0" /* 332 */ + "isScalar\0" /* 342 */ + "type\0" /* 351 */ + "phpVersion\0" /* 356 */ + "PHPStan\\Php\\PhpVersion\0" /* 367 */ + "PHPStan\\Type\\BooleanType\0" /* 390 */ + "looseCompare\0" /* 415 */ + "typeToRemove\0" /* 428 */ + "tryRemove\0" /* 441 */ + "getFiniteTypes\0" /* 451 */ + "exponent\0" /* 466 */ + "arithmeticPower\0" /* 475 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 491 */ + "toPhpDocNode\0" /* 530 */ + "position\0" /* 543 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 552 */ + "toNativePhpTypeNode\0" /* 584 */ + "hasTemplateOrLateResolvableType"; /* 604 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ reg::packed(46, MAY_BE_ARRAY), /* getConstantStrings return */ reg::packed(46, 0, 75), /* toNumber return */ + reg::packed(46, 0, 102), /* toNumberSet return */ reg::packed(46, 0, 75), /* toBitwiseNotType return */ reg::packed(46, 0, 75), /* toAbsoluteNumber return */ reg::packed(46, 0, 75), /* toFloat return */ @@ -92,27 +95,27 @@ inline constexpr reg::PackedArg args[] = { reg::packed(46, 0, 75), /* toString return */ reg::packed(46, 0, 75), /* toArray return */ reg::packed(46, 0, 75), /* toArrayKey return */ - reg::packed(182, MAY_BE_BOOL), /* toCoercedArgumentType $strictTypes */ + reg::packed(217, MAY_BE_BOOL), /* toCoercedArgumentType $strictTypes */ reg::packed(46, 0, 75), /* toCoercedArgumentType return */ - reg::packed(46, 0, 216), /* isOffsetAccessLegal return */ - reg::packed(46, 0, 216), /* isNull return */ - reg::packed(46, 0, 216), /* isTrue return */ - reg::packed(46, 0, 216), /* isFalse return */ - reg::packed(46, 0, 216), /* isBoolean return */ - reg::packed(46, 0, 216), /* isFloat return */ - reg::packed(46, 0, 216), /* isInteger return */ - reg::packed(46, 0, 216), /* isScalar return */ - reg::packed(316, 0, 75), /* looseCompare $type */ - reg::packed(321, 0, 332), /* looseCompare $phpVersion */ - reg::packed(46, 0, 355), /* looseCompare return */ - reg::packed(393, 0, 75), /* tryRemove $typeToRemove */ + reg::packed(46, 0, 251), /* isOffsetAccessLegal return */ + reg::packed(46, 0, 251), /* isNull return */ + reg::packed(46, 0, 251), /* isTrue return */ + reg::packed(46, 0, 251), /* isFalse return */ + reg::packed(46, 0, 251), /* isBoolean return */ + reg::packed(46, 0, 251), /* isFloat return */ + reg::packed(46, 0, 251), /* isInteger return */ + reg::packed(46, 0, 251), /* isScalar return */ + reg::packed(351, 0, 75), /* looseCompare $type */ + reg::packed(356, 0, 367), /* looseCompare $phpVersion */ + reg::packed(46, 0, 390), /* looseCompare return */ + reg::packed(428, 0, 75), /* tryRemove $typeToRemove */ reg::packed(46, MAY_BE_NULL, 75), /* tryRemove return */ reg::packed(46, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(431, 0, 75), /* arithmeticPower $exponent */ - reg::packed(321, 0, 332), /* arithmeticPower $phpVersion */ + reg::packed(466, 0, 75), /* arithmeticPower $exponent */ + reg::packed(356, 0, 367), /* arithmeticPower $phpVersion */ reg::packed(46, 0, 75), /* arithmeticPower return */ - reg::packed(46, 0, 456), /* toPhpDocNode return */ - reg::packed(508, 0, 517), /* toNativePhpTypeNode $position */ + reg::packed(46, 0, 491), /* toPhpDocNode return */ + reg::packed(543, 0, 552), /* toNativePhpTypeNode $position */ reg::packed(46, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -124,29 +127,30 @@ inline constexpr sigtab::Sig __construct = { { 0 /* __construct */, 0, 0, 0, reg inline constexpr sigtab::Sig describe = { { 47 /* describe */, 1, 0, 1, 1, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getConstantStrings = { { 56 /* getConstantStrings */, 0, 2, 0, 2, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toNumber = { { 93 /* toNumber */, 0, 3, 0, 3, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 102 /* toBitwiseNotType */, 0, 4, 0, 4, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 119 /* toAbsoluteNumber */, 0, 5, 0, 5, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toFloat = { { 136 /* toFloat */, 0, 6, 0, 6, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toInteger = { { 144 /* toInteger */, 0, 7, 0, 7, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 154 /* toString */, 0, 8, 0, 8, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArray = { { 163 /* toArray */, 0, 9, 0, 9, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArrayKey = { { 171 /* toArrayKey */, 0, 10, 0, 10, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toCoercedArgumentType = { { 194 /* toCoercedArgumentType */, 1, 11, 1, 12, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isOffsetAccessLegal = { { 237 /* isOffsetAccessLegal */, 0, 13, 0, 13, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNull = { { 257 /* isNull */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isTrue = { { 264 /* isTrue */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isFalse = { { 271 /* isFalse */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isBoolean = { { 279 /* isBoolean */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isFloat = { { 289 /* isFloat */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInteger = { { 297 /* isInteger */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isScalar = { { 307 /* isScalar */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 380 /* looseCompare */, 2, 21, 2, 23, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryRemove = { { 406 /* tryRemove */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 416 /* getFiniteTypes */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticPower = { { 440 /* arithmeticPower */, 2, 27, 2, 29, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 495 /* toPhpDocNode */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 549 /* toNativePhpTypeNode */, 1, 31, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 569 /* hasTemplateOrLateResolvableType */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 125 /* toNumberSet */, 0, 4, 0, 4, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 137 /* toBitwiseNotType */, 0, 5, 0, 5, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 154 /* toAbsoluteNumber */, 0, 6, 0, 6, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 171 /* toFloat */, 0, 7, 0, 7, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 179 /* toInteger */, 0, 8, 0, 8, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 189 /* toString */, 0, 9, 0, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArray = { { 198 /* toArray */, 0, 10, 0, 10, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 206 /* toArrayKey */, 0, 11, 0, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toCoercedArgumentType = { { 229 /* toCoercedArgumentType */, 1, 12, 1, 13, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isOffsetAccessLegal = { { 272 /* isOffsetAccessLegal */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNull = { { 292 /* isNull */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isTrue = { { 299 /* isTrue */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFalse = { { 306 /* isFalse */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isBoolean = { { 314 /* isBoolean */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFloat = { { 324 /* isFloat */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInteger = { { 332 /* isInteger */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isScalar = { { 342 /* isScalar */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 415 /* looseCompare */, 2, 22, 2, 24, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryRemove = { { 441 /* tryRemove */, 1, 25, 1, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 451 /* getFiniteTypes */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 475 /* arithmeticPower */, 2, 28, 2, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 530 /* toPhpDocNode */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 584 /* toNativePhpTypeNode */, 1, 32, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 604 /* hasTemplateOrLateResolvableType */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntegerType diff --git a/turbo-ext/src/generated/NumberSet.h b/turbo-ext/src/generated/NumberSet.h new file mode 100644 index 00000000000..69ed1f28b54 --- /dev/null +++ b/turbo-ext/src/generated/NumberSet.h @@ -0,0 +1,63 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Type/NumberSet.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_NUMBER_SET_H +#define PHPSTANTURBO_GENERATED_NUMBER_SET_H + +#include "../reg.h" + +namespace ptdecl::NumberSet { + +/* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ +namespace slot { +inline constexpr uint32_t integerIntervals = 0; +inline constexpr uint32_t floatIntervals = 1; +inline constexpr uint32_t nan = 2; +} // namespace slot + +inline void declareClass(reg::Class &cls) +{ + cls.final(); +} + +/* the properties the class declares itself, in declaration order (a used trait's come from its registrar) */ +inline void declareProperties(reg::Class &cls) +{ + cls.property("integerIntervals", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); + cls.property("floatIntervals", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); + cls.property("nan", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "integerIntervals\0" /* 0 */ + "floatIntervals\0" /* 17 */ + "nan\0" /* 32 */ + "__construct\0" /* 36 */ + "\0" /* 48 */ + "getIntegerIntervals\0" /* 49 */ + "getFloatIntervals\0" /* 69 */ + "hasNan"; /* 87 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, MAY_BE_ARRAY), /* __construct $integerIntervals */ + reg::packed(17, MAY_BE_ARRAY), /* __construct $floatIntervals */ + reg::packed(32, MAY_BE_BOOL), /* __construct $nan */ + reg::packed(48, MAY_BE_ARRAY), /* getIntegerIntervals return */ + reg::packed(48, MAY_BE_ARRAY), /* getFloatIntervals return */ + reg::packed(48, MAY_BE_BOOL), /* hasNan return */ +}; +using Sig = reg::Sig; +} // namespace sigtab + +/* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ +namespace sig { +inline constexpr sigtab::Sig __construct = { { 36 /* __construct */, 3, 0, 3, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getIntegerIntervals = { { 49 /* getIntegerIntervals */, 0, 3, 0, 3, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFloatIntervals = { { 69 /* getFloatIntervals */, 0, 4, 0, 4, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasNan = { { 87 /* hasNan */, 0, 5, 0, 5, ZEND_ACC_PUBLIC } }; +} // namespace sig + +} // namespace ptdecl::NumberSet + +#endif diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index 70aeadebf00..45965ca210b 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -49,55 +49,59 @@ inline constexpr char strings[] = "divisorMax\0" /* 310 */ "getMaxModuloMagnitude\0" /* 321 */ "getIntegerBounds\0" /* 343 */ - "add\0" /* 360 */ - "arrayUnion\0" /* 364 */ - "tryArrayUnion\0" /* 375 */ - "subtract\0" /* 389 */ - "multiply\0" /* 398 */ - "shiftLeft\0" /* 407 */ - "shiftRight\0" /* 417 */ - "optimizeScalarType\0" /* 428 */ - "getNonNegativeIntegerBounds\0" /* 447 */ - "leftNumberType\0" /* 475 */ - "rightNumberType\0" /* 490 */ - "computeBitwiseAndRange\0" /* 506 */ - "computeBitwiseOrXorRange\0" /* 529 */ - "value\0" /* 554 */ - "allBitsMask\0" /* 560 */ - "operator\0" /* 572 */ - "resolveCommonMath\0" /* 581 */ - "range\0" /* 599 */ - "operand\0" /* 605 */ - "integerRangeMath\0" /* 613 */ - "shift\0" /* 630 */ - "shiftLeftOverflows\0" /* 636 */ - "negateNumber\0" /* 655 */ - "toIntBound\0" /* 668 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 679 */ - "toIntegerOperand\0" /* 711 */ - "getTypeFromValue\0" /* 728 */ - "floatRangeMath\0" /* 745 */ - "interval\0" /* 760 */ - "splitIntegerDivisor\0" /* 769 */ - "getFloatIntervalMembers\0" /* 789 */ - "floatIntervalMath\0" /* 813 */ - "calculateFloat\0" /* 831 */ - "bound\0" /* 846 */ - "min\0" /* 852 */ - "max\0" /* 856 */ - "getFloatBoundWithInnerNeighbour\0" /* 860 */ - "getNeverType\0" /* 892 */ - "negate\0" /* 905 */ - "numeratorType\0" /* 912 */ - "divisorType\0" /* 926 */ - "intDivide\0" /* 938 */ - "getIntervals\0" /* 948 */ - "numeratorMin\0" /* 961 */ - "numeratorMax\0" /* 974 */ - "divideIntervals\0" /* 987 */ - "numerator\0" /* 1003 */ - "divisor\0" /* 1013 */ - "divideBounds"; /* 1021 */ + "getIntegerIntervals\0" /* 360 */ + "add\0" /* 380 */ + "arrayUnion\0" /* 384 */ + "tryArrayUnion\0" /* 395 */ + "subtract\0" /* 409 */ + "multiply\0" /* 418 */ + "shiftLeft\0" /* 427 */ + "shiftRight\0" /* 437 */ + "optimizeScalarType\0" /* 448 */ + "getNonNegativeIntegerBounds\0" /* 467 */ + "leftNumberType\0" /* 495 */ + "rightNumberType\0" /* 510 */ + "computeBitwiseAndRange\0" /* 526 */ + "computeBitwiseOrXorRange\0" /* 549 */ + "value\0" /* 574 */ + "allBitsMask\0" /* 580 */ + "operator\0" /* 592 */ + "resolveCommonMath\0" /* 601 */ + "range\0" /* 619 */ + "operand\0" /* 625 */ + "integerRangeMath\0" /* 633 */ + "shift\0" /* 650 */ + "shiftLeftOverflows\0" /* 656 */ + "negateNumber\0" /* 675 */ + "toIntBound\0" /* 688 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 699 */ + "toIntegerOperand\0" /* 731 */ + "getTypeFromValue\0" /* 748 */ + "floatRangeMath\0" /* 765 */ + "interval\0" /* 780 */ + "splitIntegerDivisor\0" /* 789 */ + "getFloatIntervalMembers\0" /* 809 */ + "numberSet\0" /* 833 */ + "PHPStan\\Type\\NumberSet\0" /* 843 */ + "isPlainFloat\0" /* 866 */ + "floatIntervalMath\0" /* 879 */ + "calculateFloat\0" /* 897 */ + "bound\0" /* 912 */ + "min\0" /* 918 */ + "max\0" /* 922 */ + "getFloatBoundWithInnerNeighbour\0" /* 926 */ + "getNeverType\0" /* 958 */ + "negate\0" /* 971 */ + "numeratorType\0" /* 978 */ + "divisorType\0" /* 992 */ + "intDivide\0" /* 1004 */ + "getIntervals\0" /* 1014 */ + "numeratorMin\0" /* 1027 */ + "numeratorMax\0" /* 1040 */ + "divideIntervals\0" /* 1053 */ + "numerator\0" /* 1069 */ + "divisor\0" /* 1079 */ + "divideBounds"; /* 1087 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* concat $left */ reg::packed(23, 0, 5), /* concat $right */ @@ -146,6 +150,8 @@ inline constexpr reg::PackedArg args[] = { reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude return */ reg::packed(240, 0, 5), /* getIntegerBounds $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerBounds return */ + reg::packed(240, 0, 5), /* getIntegerIntervals $type */ + reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerIntervals return */ reg::packed(105, 0, 5), /* add $leftType */ reg::packed(114, 0, 5), /* add $rightType */ reg::packed(29, 0, 40), /* add $phpVersion */ @@ -177,54 +183,56 @@ inline constexpr reg::PackedArg args[] = { reg::packed(63, 0, 5), /* optimizeScalarType return */ reg::packed(240, 0, 5), /* getNonNegativeIntegerBounds $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ - reg::packed(475, 0, 5), /* computeBitwiseAndRange $leftNumberType */ - reg::packed(490, 0, 5), /* computeBitwiseAndRange $rightNumberType */ + reg::packed(495, 0, 5), /* computeBitwiseAndRange $leftNumberType */ + reg::packed(510, 0, 5), /* computeBitwiseAndRange $rightNumberType */ reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseAndRange return */ - reg::packed(475, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ - reg::packed(490, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ + reg::packed(495, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ + reg::packed(510, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseOrXorRange return */ - reg::packed(554, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(574, MAY_BE_LONG), /* allBitsMask $value */ reg::packed(63, MAY_BE_LONG), /* allBitsMask return */ - reg::packed(572, MAY_BE_STRING), /* resolveCommonMath $operator */ + reg::packed(592, MAY_BE_STRING), /* resolveCommonMath $operator */ reg::packed(105, 0, 5), /* resolveCommonMath $leftType */ reg::packed(114, 0, 5), /* resolveCommonMath $rightType */ reg::packed(63, 0, 5), /* resolveCommonMath return */ - reg::packed(599, 0, 5), /* integerRangeMath $range */ - reg::packed(572, MAY_BE_STRING), /* integerRangeMath $operator */ - reg::packed(605, 0, 5), /* integerRangeMath $operand */ + reg::packed(619, 0, 5), /* integerRangeMath $range */ + reg::packed(592, MAY_BE_STRING), /* integerRangeMath $operator */ + reg::packed(625, 0, 5), /* integerRangeMath $operand */ reg::packed(63, 0, 5), /* integerRangeMath return */ - reg::packed(554, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(630, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(574, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(650, MAY_BE_LONG), /* shiftLeftOverflows $shift */ reg::packed(63, MAY_BE_BOOL), /* shiftLeftOverflows return */ reg::packed(240, 0, 5), /* negateNumber $type */ reg::packed(63, 0, 5), /* negateNumber return */ - reg::packed(554, MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(574, MAY_BE_DOUBLE), /* toIntBound $value */ reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ - reg::packed(240, 0, 679), /* toIntegerOperand $type */ + reg::packed(240, 0, 699), /* toIntegerOperand $type */ reg::packed(63, MAY_BE_LONG), /* toIntegerOperand return */ - reg::packed(554, 0), /* getTypeFromValue $value */ + reg::packed(574, 0), /* getTypeFromValue $value */ reg::packed(63, 0, 5), /* getTypeFromValue return */ - reg::packed(572, MAY_BE_STRING), /* floatRangeMath $operator */ + reg::packed(592, MAY_BE_STRING), /* floatRangeMath $operator */ reg::packed(105, 0, 5), /* floatRangeMath $leftType */ reg::packed(114, 0, 5), /* floatRangeMath $rightType */ reg::packed(29, 0, 40), /* floatRangeMath $phpVersion */ reg::packed(63, MAY_BE_NULL, 5), /* floatRangeMath return */ - reg::packed(760, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ + reg::packed(780, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ reg::packed(63, MAY_BE_ARRAY), /* splitIntegerDivisor return */ reg::packed(240, 0, 5), /* getFloatIntervalMembers $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getFloatIntervalMembers return */ - reg::packed(572, MAY_BE_STRING), /* floatIntervalMath $operator */ + reg::packed(833, 0, 843), /* isPlainFloat $numberSet */ + reg::packed(63, MAY_BE_BOOL), /* isPlainFloat return */ + reg::packed(592, MAY_BE_STRING), /* floatIntervalMath $operator */ reg::packed(0, MAY_BE_ARRAY), /* floatIntervalMath $left */ reg::packed(23, MAY_BE_ARRAY), /* floatIntervalMath $right */ reg::packed(29, 0, 40), /* floatIntervalMath $phpVersion */ reg::packed(63, 0, 5), /* floatIntervalMath return */ - reg::packed(572, MAY_BE_STRING), /* calculateFloat $operator */ + reg::packed(592, MAY_BE_STRING), /* calculateFloat $operator */ reg::packed(0, MAY_BE_DOUBLE), /* calculateFloat $left */ reg::packed(23, MAY_BE_DOUBLE), /* calculateFloat $right */ reg::packed(63, MAY_BE_DOUBLE), /* calculateFloat return */ - reg::packed(846, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ - reg::packed(852, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ - reg::packed(856, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ + reg::packed(912, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ + reg::packed(918, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ + reg::packed(922, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ reg::packed(63, MAY_BE_ARRAY), /* getFloatBoundWithInnerNeighbour return */ reg::packed(105, 0, 5), /* getNeverType $leftType */ reg::packed(114, 0, 5), /* getNeverType $rightType */ @@ -232,19 +240,19 @@ inline constexpr reg::PackedArg args[] = { reg::packed(240, 0, 5), /* negate $type */ reg::packed(29, 0, 40), /* negate $phpVersion */ reg::packed(63, 0, 5), /* negate return */ - reg::packed(912, 0, 5), /* intDivide $numeratorType */ - reg::packed(926, 0, 5), /* intDivide $divisorType */ + reg::packed(978, 0, 5), /* intDivide $numeratorType */ + reg::packed(992, 0, 5), /* intDivide $divisorType */ reg::packed(29, 0, 40), /* intDivide $phpVersion */ reg::packed(63, 0, 5), /* intDivide return */ reg::packed(240, 0, 5), /* getIntervals $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntervals return */ - reg::packed(961, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ - reg::packed(974, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ + reg::packed(1027, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ + reg::packed(1040, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ reg::packed(299, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ reg::packed(310, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ reg::packed(63, 0, 5), /* divideIntervals return */ - reg::packed(1003, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ - reg::packed(1013, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ + reg::packed(1069, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ + reg::packed(1079, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ reg::packed(63, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds return */ }; using Sig = reg::Sig; @@ -265,37 +273,39 @@ inline constexpr sigtab::Sig getDivTypeFromTypes = { { 272 /* getDivTypeFromType inline constexpr sigtab::Sig modulo = { { 292 /* modulo */, 3, 38, 3, 41, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 321 /* getMaxModuloMagnitude */, 2, 42, 2, 44, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig getIntegerBounds = { { 343 /* getIntegerBounds */, 1, 45, 1, 46, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig add = { { 360 /* add */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 364 /* arrayUnion */, 3, 51, 3, 54, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 375 /* tryArrayUnion */, 2, 55, 2, 57, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig subtract = { { 389 /* subtract */, 3, 58, 3, 61, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig multiply = { { 398 /* multiply */, 3, 62, 3, 65, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 407 /* shiftLeft */, 3, 66, 3, 69, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftRight = { { 417 /* shiftRight */, 3, 70, 3, 73, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 428 /* optimizeScalarType */, 1, 74, 1, 75, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 447 /* getNonNegativeIntegerBounds */, 1, 76, 1, 77, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseAndRange = { { 506 /* computeBitwiseAndRange */, 2, 78, 2, 80, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 529 /* computeBitwiseOrXorRange */, 2, 81, 2, 83, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig allBitsMask = { { 560 /* allBitsMask */, 1, 84, 1, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 581 /* resolveCommonMath */, 3, 86, 3, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig integerRangeMath = { { 613 /* integerRangeMath */, 3, 90, 3, 93, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 636 /* shiftLeftOverflows */, 2, 94, 2, 96, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negateNumber = { { 655 /* negateNumber */, 1, 97, 1, 98, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntBound = { { 668 /* toIntBound */, 1, 99, 1, 100, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntegerOperand = { { 711 /* toIntegerOperand */, 1, 101, 1, 102, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 728 /* getTypeFromValue */, 1, 103, 1, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig floatRangeMath = { { 745 /* floatRangeMath */, 4, 105, 4, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig splitIntegerDivisor = { { 769 /* splitIntegerDivisor */, 1, 110, 1, 111, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatIntervalMembers = { { 789 /* getFloatIntervalMembers */, 1, 112, 1, 113, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig floatIntervalMath = { { 813 /* floatIntervalMath */, 4, 114, 4, 118, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloat = { { 831 /* calculateFloat */, 3, 119, 3, 122, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 860 /* getFloatBoundWithInnerNeighbour */, 3, 123, 3, 126, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 892 /* getNeverType */, 2, 127, 2, 129, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 905 /* negate */, 2, 130, 2, 132, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 938 /* intDivide */, 3, 133, 3, 136, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntervals = { { 948 /* getIntervals */, 1, 137, 1, 138, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntervals = { { 987 /* divideIntervals */, 4, 139, 4, 143, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBounds = { { 1021 /* divideBounds */, 2, 144, 2, 146, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerIntervals = { { 360 /* getIntegerIntervals */, 1, 47, 1, 48, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig add = { { 380 /* add */, 3, 49, 3, 52, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 384 /* arrayUnion */, 3, 53, 3, 56, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 395 /* tryArrayUnion */, 2, 57, 2, 59, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig subtract = { { 409 /* subtract */, 3, 60, 3, 63, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig multiply = { { 418 /* multiply */, 3, 64, 3, 67, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 427 /* shiftLeft */, 3, 68, 3, 71, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftRight = { { 437 /* shiftRight */, 3, 72, 3, 75, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig optimizeScalarType = { { 448 /* optimizeScalarType */, 1, 76, 1, 77, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 467 /* getNonNegativeIntegerBounds */, 1, 78, 1, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseAndRange = { { 526 /* computeBitwiseAndRange */, 2, 80, 2, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 549 /* computeBitwiseOrXorRange */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig allBitsMask = { { 580 /* allBitsMask */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolveCommonMath = { { 601 /* resolveCommonMath */, 3, 88, 3, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig integerRangeMath = { { 633 /* integerRangeMath */, 3, 92, 3, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 656 /* shiftLeftOverflows */, 2, 96, 2, 98, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negateNumber = { { 675 /* negateNumber */, 1, 99, 1, 100, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 688 /* toIntBound */, 1, 101, 1, 102, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntegerOperand = { { 731 /* toIntegerOperand */, 1, 103, 1, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 748 /* getTypeFromValue */, 1, 105, 1, 106, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig floatRangeMath = { { 765 /* floatRangeMath */, 4, 107, 4, 111, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig splitIntegerDivisor = { { 789 /* splitIntegerDivisor */, 1, 112, 1, 113, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFloatIntervalMembers = { { 809 /* getFloatIntervalMembers */, 1, 114, 1, 115, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isPlainFloat = { { 866 /* isPlainFloat */, 1, 116, 1, 117, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig floatIntervalMath = { { 879 /* floatIntervalMath */, 4, 118, 4, 122, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloat = { { 897 /* calculateFloat */, 3, 123, 3, 126, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 926 /* getFloatBoundWithInnerNeighbour */, 3, 127, 3, 130, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNeverType = { { 958 /* getNeverType */, 2, 131, 2, 133, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negate = { { 971 /* negate */, 2, 134, 2, 136, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 1004 /* intDivide */, 3, 137, 3, 140, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntervals = { { 1014 /* getIntervals */, 1, 141, 1, 142, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntervals = { { 1053 /* divideIntervals */, 4, 143, 4, 147, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBounds = { { 1087 /* divideBounds */, 2, 148, 2, 150, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper diff --git a/turbo-ext/src/generated/OperatorTypeTrait.h b/turbo-ext/src/generated/OperatorTypeTrait.h index a88c5594a35..bbdf424b01c 100644 --- a/turbo-ext/src/generated/OperatorTypeTrait.h +++ b/turbo-ext/src/generated/OperatorTypeTrait.h @@ -15,72 +15,75 @@ inline constexpr char strings[] = "PHPStan\\Type\\Type\0" /* 9 */ "\0" /* 27 */ "exponentiate\0" /* 28 */ - "right\0" /* 41 */ - "phpVersion\0" /* 47 */ - "PHPStan\\Php\\PhpVersion\0" /* 58 */ - "arithmeticAdd\0" /* 81 */ - "arithmeticSubtract\0" /* 95 */ - "arithmeticMultiply\0" /* 114 */ - "arithmeticDivide\0" /* 133 */ - "arithmeticIntDivide\0" /* 150 */ - "arithmeticModulo\0" /* 170 */ - "arithmeticNegate\0" /* 187 */ - "bitwiseAnd\0" /* 204 */ - "bitwiseOr\0" /* 215 */ - "bitwiseXor\0" /* 225 */ - "bitwiseShiftLeft\0" /* 236 */ - "bitwiseShiftRight\0" /* 253 */ - "getPossiblyLooselyEqualType\0" /* 271 */ - "getAlwaysLooselyEqualType\0" /* 299 */ - "stringConcatenate\0" /* 325 */ - "arrayUnion"; /* 343 */ + "PHPStan\\Type\\NumberSet\0" /* 41 */ + "toNumberSet\0" /* 64 */ + "right\0" /* 76 */ + "phpVersion\0" /* 82 */ + "PHPStan\\Php\\PhpVersion\0" /* 93 */ + "arithmeticAdd\0" /* 116 */ + "arithmeticSubtract\0" /* 130 */ + "arithmeticMultiply\0" /* 149 */ + "arithmeticDivide\0" /* 168 */ + "arithmeticIntDivide\0" /* 185 */ + "arithmeticModulo\0" /* 205 */ + "arithmeticNegate\0" /* 222 */ + "bitwiseAnd\0" /* 239 */ + "bitwiseOr\0" /* 250 */ + "bitwiseXor\0" /* 260 */ + "bitwiseShiftLeft\0" /* 271 */ + "bitwiseShiftRight\0" /* 288 */ + "getPossiblyLooselyEqualType\0" /* 306 */ + "getAlwaysLooselyEqualType\0" /* 334 */ + "stringConcatenate\0" /* 360 */ + "arrayUnion"; /* 378 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* exponentiate $exponent */ reg::packed(27, 0, 9), /* exponentiate return */ - reg::packed(41, 0, 9), /* arithmeticAdd $right */ - reg::packed(47, 0, 58), /* arithmeticAdd $phpVersion */ + reg::packed(27, MAY_BE_NULL, 41), /* toNumberSet return */ + reg::packed(76, 0, 9), /* arithmeticAdd $right */ + reg::packed(82, 0, 93), /* arithmeticAdd $phpVersion */ reg::packed(27, 0, 9), /* arithmeticAdd return */ - reg::packed(41, 0, 9), /* arithmeticSubtract $right */ - reg::packed(47, 0, 58), /* arithmeticSubtract $phpVersion */ + reg::packed(76, 0, 9), /* arithmeticSubtract $right */ + reg::packed(82, 0, 93), /* arithmeticSubtract $phpVersion */ reg::packed(27, 0, 9), /* arithmeticSubtract return */ - reg::packed(41, 0, 9), /* arithmeticMultiply $right */ - reg::packed(47, 0, 58), /* arithmeticMultiply $phpVersion */ + reg::packed(76, 0, 9), /* arithmeticMultiply $right */ + reg::packed(82, 0, 93), /* arithmeticMultiply $phpVersion */ reg::packed(27, 0, 9), /* arithmeticMultiply return */ - reg::packed(41, 0, 9), /* arithmeticDivide $right */ - reg::packed(47, 0, 58), /* arithmeticDivide $phpVersion */ + reg::packed(76, 0, 9), /* arithmeticDivide $right */ + reg::packed(82, 0, 93), /* arithmeticDivide $phpVersion */ reg::packed(27, 0, 9), /* arithmeticDivide return */ - reg::packed(41, 0, 9), /* arithmeticIntDivide $right */ - reg::packed(47, 0, 58), /* arithmeticIntDivide $phpVersion */ + reg::packed(76, 0, 9), /* arithmeticIntDivide $right */ + reg::packed(82, 0, 93), /* arithmeticIntDivide $phpVersion */ reg::packed(27, 0, 9), /* arithmeticIntDivide return */ - reg::packed(41, 0, 9), /* arithmeticModulo $right */ - reg::packed(47, 0, 58), /* arithmeticModulo $phpVersion */ + reg::packed(76, 0, 9), /* arithmeticModulo $right */ + reg::packed(82, 0, 93), /* arithmeticModulo $phpVersion */ reg::packed(27, 0, 9), /* arithmeticModulo return */ - reg::packed(47, 0, 58), /* arithmeticNegate $phpVersion */ + reg::packed(82, 0, 93), /* arithmeticNegate $phpVersion */ reg::packed(27, 0, 9), /* arithmeticNegate return */ - reg::packed(41, 0, 9), /* bitwiseAnd $right */ - reg::packed(47, 0, 58), /* bitwiseAnd $phpVersion */ + reg::packed(76, 0, 9), /* bitwiseAnd $right */ + reg::packed(82, 0, 93), /* bitwiseAnd $phpVersion */ reg::packed(27, 0, 9), /* bitwiseAnd return */ - reg::packed(41, 0, 9), /* bitwiseOr $right */ - reg::packed(47, 0, 58), /* bitwiseOr $phpVersion */ + reg::packed(76, 0, 9), /* bitwiseOr $right */ + reg::packed(82, 0, 93), /* bitwiseOr $phpVersion */ reg::packed(27, 0, 9), /* bitwiseOr return */ - reg::packed(41, 0, 9), /* bitwiseXor $right */ - reg::packed(47, 0, 58), /* bitwiseXor $phpVersion */ + reg::packed(76, 0, 9), /* bitwiseXor $right */ + reg::packed(82, 0, 93), /* bitwiseXor $phpVersion */ reg::packed(27, 0, 9), /* bitwiseXor return */ - reg::packed(41, 0, 9), /* bitwiseShiftLeft $right */ - reg::packed(47, 0, 58), /* bitwiseShiftLeft $phpVersion */ + reg::packed(76, 0, 9), /* bitwiseShiftLeft $right */ + reg::packed(82, 0, 93), /* bitwiseShiftLeft $phpVersion */ reg::packed(27, 0, 9), /* bitwiseShiftLeft return */ - reg::packed(41, 0, 9), /* bitwiseShiftRight $right */ - reg::packed(47, 0, 58), /* bitwiseShiftRight $phpVersion */ + reg::packed(76, 0, 9), /* bitwiseShiftRight $right */ + reg::packed(82, 0, 93), /* bitwiseShiftRight $phpVersion */ reg::packed(27, 0, 9), /* bitwiseShiftRight return */ - reg::packed(47, 0, 58), /* getPossiblyLooselyEqualType $phpVersion */ + reg::packed(82, 0, 93), /* getPossiblyLooselyEqualType $phpVersion */ reg::packed(27, 0, 9), /* getPossiblyLooselyEqualType return */ - reg::packed(47, 0, 58), /* getAlwaysLooselyEqualType $phpVersion */ + reg::packed(82, 0, 93), /* getAlwaysLooselyEqualType $phpVersion */ reg::packed(27, 0, 9), /* getAlwaysLooselyEqualType return */ - reg::packed(41, 0, 9), /* stringConcatenate $right */ - reg::packed(47, 0, 58), /* stringConcatenate $phpVersion */ + reg::packed(76, 0, 9), /* stringConcatenate $right */ + reg::packed(82, 0, 93), /* stringConcatenate $phpVersion */ reg::packed(27, 0, 9), /* stringConcatenate return */ - reg::packed(41, 0, 9), /* arrayUnion $right */ - reg::packed(47, 0, 58), /* arrayUnion $phpVersion */ + reg::packed(76, 0, 9), /* arrayUnion $right */ + reg::packed(82, 0, 93), /* arrayUnion $phpVersion */ reg::packed(27, 0, 9), /* arrayUnion return */ }; using Sig = reg::Sig; @@ -89,22 +92,23 @@ using Sig = reg::Sig; /* the signatures of the methods the trait declares itself */ namespace sig { inline constexpr sigtab::Sig exponentiate = { { 28 /* exponentiate */, 1, 0, 1, 1, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticAdd = { { 81 /* arithmeticAdd */, 2, 2, 2, 4, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticSubtract = { { 95 /* arithmeticSubtract */, 2, 5, 2, 7, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticMultiply = { { 114 /* arithmeticMultiply */, 2, 8, 2, 10, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticDivide = { { 133 /* arithmeticDivide */, 2, 11, 2, 13, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticIntDivide = { { 150 /* arithmeticIntDivide */, 2, 14, 2, 16, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticModulo = { { 170 /* arithmeticModulo */, 2, 17, 2, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticNegate = { { 187 /* arithmeticNegate */, 1, 20, 1, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseAnd = { { 204 /* bitwiseAnd */, 2, 22, 2, 24, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseOr = { { 215 /* bitwiseOr */, 2, 25, 2, 27, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseXor = { { 225 /* bitwiseXor */, 2, 28, 2, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftLeft = { { 236 /* bitwiseShiftLeft */, 2, 31, 2, 33, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftRight = { { 253 /* bitwiseShiftRight */, 2, 34, 2, 36, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 271 /* getPossiblyLooselyEqualType */, 1, 37, 1, 38, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 299 /* getAlwaysLooselyEqualType */, 1, 39, 1, 40, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig stringConcatenate = { { 325 /* stringConcatenate */, 2, 41, 2, 43, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 343 /* arrayUnion */, 2, 44, 2, 46, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 64 /* toNumberSet */, 0, 2, 0, 2, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticAdd = { { 116 /* arithmeticAdd */, 2, 3, 2, 5, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticSubtract = { { 130 /* arithmeticSubtract */, 2, 6, 2, 8, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticMultiply = { { 149 /* arithmeticMultiply */, 2, 9, 2, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticDivide = { { 168 /* arithmeticDivide */, 2, 12, 2, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticIntDivide = { { 185 /* arithmeticIntDivide */, 2, 15, 2, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticModulo = { { 205 /* arithmeticModulo */, 2, 18, 2, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticNegate = { { 222 /* arithmeticNegate */, 1, 21, 1, 22, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 239 /* bitwiseAnd */, 2, 23, 2, 25, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 250 /* bitwiseOr */, 2, 26, 2, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 260 /* bitwiseXor */, 2, 29, 2, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftLeft = { { 271 /* bitwiseShiftLeft */, 2, 32, 2, 34, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftRight = { { 288 /* bitwiseShiftRight */, 2, 35, 2, 37, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 306 /* getPossiblyLooselyEqualType */, 1, 38, 1, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 334 /* getAlwaysLooselyEqualType */, 1, 40, 1, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig stringConcatenate = { { 360 /* stringConcatenate */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 378 /* arrayUnion */, 2, 45, 2, 47, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeTrait diff --git a/turbo-ext/src/generated/UnionType.h b/turbo-ext/src/generated/UnionType.h index 2b88aee4800..461c5a3467f 100644 --- a/turbo-ext/src/generated/UnionType.h +++ b/turbo-ext/src/generated/UnionType.h @@ -221,59 +221,61 @@ inline constexpr char strings[] = "isGreaterThanOrEqual\0" /* 2850 */ "toBoolean\0" /* 2871 */ "toNumber\0" /* 2881 */ - "toBitwiseNotType\0" /* 2890 */ - "toGetClassResultType\0" /* 2907 */ - "reflectionProvider\0" /* 2928 */ - "PHPStan\\Reflection\\ReflectionProvider\0" /* 2947 */ - "toClassConstantType\0" /* 2985 */ - "PHPStan\\Type\\ClassNameToObjectTypeResult\0" /* 3005 */ - "toObjectTypeForInstanceofCheck\0" /* 3046 */ - "objectOrClassType\0" /* 3077 */ - "allowString\0" /* 3095 */ - "allowSameClass\0" /* 3107 */ - "toObjectTypeForIsACheck\0" /* 3122 */ - "toAbsoluteNumber\0" /* 3146 */ - "toString\0" /* 3163 */ - "toInteger\0" /* 3172 */ - "toFloat\0" /* 3182 */ - "toArray\0" /* 3190 */ - "toArrayKey\0" /* 3198 */ - "toCoercedArgumentType\0" /* 3209 */ - "receivedType\0" /* 3231 */ - "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3244 */ - "inferTemplateTypes\0" /* 3281 */ - "PHPStan\\Type\\Generic\\TemplateType\0" /* 3300 */ - "getNakedTemplateType\0" /* 3334 */ - "inferred\0" /* 3355 */ - "getAbsorbedTemplateTypes\0" /* 3364 */ - "templateType\0" /* 3389 */ - "inferTemplateTypesOn\0" /* 3402 */ - "positionVariance\0" /* 3423 */ - "PHPStan\\Type\\Generic\\TemplateTypeVariance\0" /* 3440 */ - "getReferencedTemplateTypes\0" /* 3482 */ - "traverse\0" /* 3509 */ - "right\0" /* 3518 */ - "traverseSimultaneously\0" /* 3524 */ - "typeToRemove\0" /* 3547 */ - "tryRemove\0" /* 3560 */ - "exponent\0" /* 3570 */ - "arithmeticPower\0" /* 3579 */ - "getFiniteTypes\0" /* 3595 */ - "getResult\0" /* 3610 */ - "unionResults\0" /* 3620 */ - "notBenevolentUnionResults\0" /* 3633 */ - "getType\0" /* 3659 */ - "unionTypes\0" /* 3667 */ - "getValues\0" /* 3678 */ - "criteria\0" /* 3688 */ - "pickFromTypes\0" /* 3697 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3711 */ - "toPhpDocNode\0" /* 3750 */ - "position\0" /* 3763 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3772 */ - "toNativePhpTypeNode\0" /* 3804 */ - "notBenevolentPickFromTypes\0" /* 3824 */ - "hasTemplateOrLateResolvableType"; /* 3851 */ + "PHPStan\\Type\\NumberSet\0" /* 2890 */ + "toNumberSet\0" /* 2913 */ + "toBitwiseNotType\0" /* 2925 */ + "toGetClassResultType\0" /* 2942 */ + "reflectionProvider\0" /* 2963 */ + "PHPStan\\Reflection\\ReflectionProvider\0" /* 2982 */ + "toClassConstantType\0" /* 3020 */ + "PHPStan\\Type\\ClassNameToObjectTypeResult\0" /* 3040 */ + "toObjectTypeForInstanceofCheck\0" /* 3081 */ + "objectOrClassType\0" /* 3112 */ + "allowString\0" /* 3130 */ + "allowSameClass\0" /* 3142 */ + "toObjectTypeForIsACheck\0" /* 3157 */ + "toAbsoluteNumber\0" /* 3181 */ + "toString\0" /* 3198 */ + "toInteger\0" /* 3207 */ + "toFloat\0" /* 3217 */ + "toArray\0" /* 3225 */ + "toArrayKey\0" /* 3233 */ + "toCoercedArgumentType\0" /* 3244 */ + "receivedType\0" /* 3266 */ + "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3279 */ + "inferTemplateTypes\0" /* 3316 */ + "PHPStan\\Type\\Generic\\TemplateType\0" /* 3335 */ + "getNakedTemplateType\0" /* 3369 */ + "inferred\0" /* 3390 */ + "getAbsorbedTemplateTypes\0" /* 3399 */ + "templateType\0" /* 3424 */ + "inferTemplateTypesOn\0" /* 3437 */ + "positionVariance\0" /* 3458 */ + "PHPStan\\Type\\Generic\\TemplateTypeVariance\0" /* 3475 */ + "getReferencedTemplateTypes\0" /* 3517 */ + "traverse\0" /* 3544 */ + "right\0" /* 3553 */ + "traverseSimultaneously\0" /* 3559 */ + "typeToRemove\0" /* 3582 */ + "tryRemove\0" /* 3595 */ + "exponent\0" /* 3605 */ + "arithmeticPower\0" /* 3614 */ + "getFiniteTypes\0" /* 3630 */ + "getResult\0" /* 3645 */ + "unionResults\0" /* 3655 */ + "notBenevolentUnionResults\0" /* 3668 */ + "getType\0" /* 3694 */ + "unionTypes\0" /* 3702 */ + "getValues\0" /* 3713 */ + "criteria\0" /* 3723 */ + "pickFromTypes\0" /* 3732 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3746 */ + "toPhpDocNode\0" /* 3785 */ + "position\0" /* 3798 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3807 */ + "toNativePhpTypeNode\0" /* 3839 */ + "notBenevolentPickFromTypes\0" /* 3859 */ + "hasTemplateOrLateResolvableType"; /* 3886 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $types */ reg::packed(6, MAY_BE_BOOL, reg::NoString, false, false, 17), /* __construct $normalized */ @@ -479,15 +481,16 @@ inline constexpr reg::PackedArg args[] = { reg::packed(35, 0, 426), /* isGreaterThanOrEqual return */ reg::packed(35, 0, 1837), /* toBoolean return */ reg::packed(35, 0, 54), /* toNumber return */ + reg::packed(35, MAY_BE_NULL, 2890), /* toNumberSet return */ reg::packed(35, 0, 54), /* toBitwiseNotType return */ reg::packed(35, 0, 54), /* toGetClassResultType return */ - reg::packed(2928, 0, 2947), /* toClassConstantType $reflectionProvider */ + reg::packed(2963, 0, 2982), /* toClassConstantType $reflectionProvider */ reg::packed(35, 0, 54), /* toClassConstantType return */ - reg::packed(35, 0, 3005), /* toObjectTypeForInstanceofCheck return */ - reg::packed(3077, 0, 54), /* toObjectTypeForIsACheck $objectOrClassType */ - reg::packed(3095, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowString */ - reg::packed(3107, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowSameClass */ - reg::packed(35, 0, 3005), /* toObjectTypeForIsACheck return */ + reg::packed(35, 0, 3040), /* toObjectTypeForInstanceofCheck return */ + reg::packed(3112, 0, 54), /* toObjectTypeForIsACheck $objectOrClassType */ + reg::packed(3130, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowString */ + reg::packed(3142, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowSameClass */ + reg::packed(35, 0, 3040), /* toObjectTypeForIsACheck return */ reg::packed(35, 0, 54), /* toAbsoluteNumber return */ reg::packed(35, 0, 54), /* toString return */ reg::packed(35, 0, 54), /* toInteger return */ @@ -496,39 +499,39 @@ inline constexpr reg::PackedArg args[] = { reg::packed(35, 0, 54), /* toArrayKey return */ reg::packed(275, MAY_BE_BOOL), /* toCoercedArgumentType $strictTypes */ reg::packed(35, 0, 54), /* toCoercedArgumentType return */ - reg::packed(3231, 0, 54), /* inferTemplateTypes $receivedType */ - reg::packed(35, 0, 3244), /* inferTemplateTypes return */ + reg::packed(3266, 0, 54), /* inferTemplateTypes $receivedType */ + reg::packed(35, 0, 3279), /* inferTemplateTypes return */ reg::packed(270, 0, 54), /* getNakedTemplateType $type */ - reg::packed(35, MAY_BE_NULL, 3300), /* getNakedTemplateType return */ - reg::packed(3355, 0, 3244), /* getAbsorbedTemplateTypes $inferred */ - reg::packed(35, 0, 3244), /* getAbsorbedTemplateTypes return */ - reg::packed(3389, 0, 54), /* inferTemplateTypesOn $templateType */ - reg::packed(35, 0, 3244), /* inferTemplateTypesOn return */ - reg::packed(3423, 0, 3440), /* getReferencedTemplateTypes $positionVariance */ + reg::packed(35, MAY_BE_NULL, 3335), /* getNakedTemplateType return */ + reg::packed(3390, 0, 3279), /* getAbsorbedTemplateTypes $inferred */ + reg::packed(35, 0, 3279), /* getAbsorbedTemplateTypes return */ + reg::packed(3424, 0, 54), /* inferTemplateTypesOn $templateType */ + reg::packed(35, 0, 3279), /* inferTemplateTypesOn return */ + reg::packed(3458, 0, 3475), /* getReferencedTemplateTypes $positionVariance */ reg::packed(35, MAY_BE_ARRAY), /* getReferencedTemplateTypes return */ reg::packed(2368, MAY_BE_CALLABLE), /* traverse $cb */ reg::packed(35, 0, 54), /* traverse return */ - reg::packed(3518, 0, 54), /* traverseSimultaneously $right */ + reg::packed(3553, 0, 54), /* traverseSimultaneously $right */ reg::packed(2368, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(35, 0, 54), /* traverseSimultaneously return */ - reg::packed(3547, 0, 54), /* tryRemove $typeToRemove */ + reg::packed(3582, 0, 54), /* tryRemove $typeToRemove */ reg::packed(35, MAY_BE_NULL, 54), /* tryRemove return */ - reg::packed(3570, 0, 54), /* arithmeticPower $exponent */ + reg::packed(3605, 0, 54), /* arithmeticPower $exponent */ reg::packed(1803, 0, 1814), /* arithmeticPower $phpVersion */ reg::packed(35, 0, 54), /* arithmeticPower return */ reg::packed(35, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(3610, MAY_BE_CALLABLE), /* unionResults $getResult */ + reg::packed(3645, MAY_BE_CALLABLE), /* unionResults $getResult */ reg::packed(35, 0, 426), /* unionResults return */ - reg::packed(3610, MAY_BE_CALLABLE), /* notBenevolentUnionResults $getResult */ + reg::packed(3645, MAY_BE_CALLABLE), /* notBenevolentUnionResults $getResult */ reg::packed(35, 0, 426), /* notBenevolentUnionResults return */ - reg::packed(3659, MAY_BE_CALLABLE), /* unionTypes $getType */ + reg::packed(3694, MAY_BE_CALLABLE), /* unionTypes $getType */ reg::packed(35, 0, 54), /* unionTypes return */ - reg::packed(3678, MAY_BE_CALLABLE), /* pickFromTypes $getValues */ - reg::packed(3688, MAY_BE_CALLABLE), /* pickFromTypes $criteria */ + reg::packed(3713, MAY_BE_CALLABLE), /* pickFromTypes $getValues */ + reg::packed(3723, MAY_BE_CALLABLE), /* pickFromTypes $criteria */ reg::packed(35, MAY_BE_ARRAY), /* pickFromTypes return */ - reg::packed(35, 0, 3711), /* toPhpDocNode return */ - reg::packed(3763, 0, 3772), /* toNativePhpTypeNode $position */ - reg::packed(3678, MAY_BE_CALLABLE), /* notBenevolentPickFromTypes $getValues */ + reg::packed(35, 0, 3746), /* toPhpDocNode return */ + reg::packed(3798, 0, 3807), /* toNativePhpTypeNode $position */ + reg::packed(3713, MAY_BE_CALLABLE), /* notBenevolentPickFromTypes $getValues */ reg::packed(35, MAY_BE_ARRAY), /* notBenevolentPickFromTypes return */ reg::packed(35, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; @@ -659,36 +662,37 @@ inline constexpr sigtab::Sig isGreaterThan = { { 2836 /* isGreaterThan */, 2, 19 inline constexpr sigtab::Sig isGreaterThanOrEqual = { { 2850 /* isGreaterThanOrEqual */, 2, 199, 2, 201, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toBoolean = { { 2871 /* toBoolean */, 0, 202, 0, 202, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toNumber = { { 2881 /* toNumber */, 0, 203, 0, 203, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 2890 /* toBitwiseNotType */, 0, 204, 0, 204, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toGetClassResultType = { { 2907 /* toGetClassResultType */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toClassConstantType = { { 2985 /* toClassConstantType */, 1, 206, 1, 207, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toObjectTypeForInstanceofCheck = { { 3046 /* toObjectTypeForInstanceofCheck */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toObjectTypeForIsACheck = { { 3122 /* toObjectTypeForIsACheck */, 3, 209, 3, 212, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 3146 /* toAbsoluteNumber */, 0, 213, 0, 213, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 3163 /* toString */, 0, 214, 0, 214, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toInteger = { { 3172 /* toInteger */, 0, 215, 0, 215, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toFloat = { { 3182 /* toFloat */, 0, 216, 0, 216, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArray = { { 3190 /* toArray */, 0, 217, 0, 217, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArrayKey = { { 3198 /* toArrayKey */, 0, 218, 0, 218, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toCoercedArgumentType = { { 3209 /* toCoercedArgumentType */, 1, 219, 1, 220, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig inferTemplateTypes = { { 3281 /* inferTemplateTypes */, 1, 221, 1, 222, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getNakedTemplateType = { { 3334 /* getNakedTemplateType */, 1, 223, 1, 224, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getAbsorbedTemplateTypes = { { 3364 /* getAbsorbedTemplateTypes */, 1, 225, 1, 226, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig inferTemplateTypesOn = { { 3402 /* inferTemplateTypesOn */, 1, 227, 1, 228, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3482 /* getReferencedTemplateTypes */, 1, 229, 1, 230, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig traverse = { { 3509 /* traverse */, 1, 231, 1, 232, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig traverseSimultaneously = { { 3524 /* traverseSimultaneously */, 2, 233, 2, 235, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryRemove = { { 3560 /* tryRemove */, 1, 236, 1, 237, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticPower = { { 3579 /* arithmeticPower */, 2, 238, 2, 240, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3595 /* getFiniteTypes */, 0, 241, 0, 241, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig unionResults = { { 3620 /* unionResults */, 1, 242, 1, 243, ZEND_ACC_PROTECTED } }; -inline constexpr sigtab::Sig notBenevolentUnionResults = { { 3633 /* notBenevolentUnionResults */, 1, 244, 1, 245, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig unionTypes = { { 3667 /* unionTypes */, 1, 246, 1, 247, ZEND_ACC_PROTECTED } }; -inline constexpr sigtab::Sig pickFromTypes = { { 3697 /* pickFromTypes */, 2, 248, 2, 250, ZEND_ACC_PROTECTED } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3750 /* toPhpDocNode */, 0, 251, 0, 251, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3804 /* toNativePhpTypeNode */, 1, 252, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig notBenevolentPickFromTypes = { { 3824 /* notBenevolentPickFromTypes */, 1, 253, 1, 254, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3851 /* hasTemplateOrLateResolvableType */, 0, 255, 0, 255, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumberSet = { { 2913 /* toNumberSet */, 0, 204, 0, 204, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 2925 /* toBitwiseNotType */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toGetClassResultType = { { 2942 /* toGetClassResultType */, 0, 206, 0, 206, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toClassConstantType = { { 3020 /* toClassConstantType */, 1, 207, 1, 208, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toObjectTypeForInstanceofCheck = { { 3081 /* toObjectTypeForInstanceofCheck */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toObjectTypeForIsACheck = { { 3157 /* toObjectTypeForIsACheck */, 3, 210, 3, 213, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 3181 /* toAbsoluteNumber */, 0, 214, 0, 214, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 3198 /* toString */, 0, 215, 0, 215, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 3207 /* toInteger */, 0, 216, 0, 216, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 3217 /* toFloat */, 0, 217, 0, 217, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArray = { { 3225 /* toArray */, 0, 218, 0, 218, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 3233 /* toArrayKey */, 0, 219, 0, 219, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toCoercedArgumentType = { { 3244 /* toCoercedArgumentType */, 1, 220, 1, 221, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig inferTemplateTypes = { { 3316 /* inferTemplateTypes */, 1, 222, 1, 223, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getNakedTemplateType = { { 3369 /* getNakedTemplateType */, 1, 224, 1, 225, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getAbsorbedTemplateTypes = { { 3399 /* getAbsorbedTemplateTypes */, 1, 226, 1, 227, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig inferTemplateTypesOn = { { 3437 /* inferTemplateTypesOn */, 1, 228, 1, 229, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3517 /* getReferencedTemplateTypes */, 1, 230, 1, 231, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig traverse = { { 3544 /* traverse */, 1, 232, 1, 233, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig traverseSimultaneously = { { 3559 /* traverseSimultaneously */, 2, 234, 2, 236, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryRemove = { { 3595 /* tryRemove */, 1, 237, 1, 238, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 3614 /* arithmeticPower */, 2, 239, 2, 241, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 3630 /* getFiniteTypes */, 0, 242, 0, 242, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig unionResults = { { 3655 /* unionResults */, 1, 243, 1, 244, ZEND_ACC_PROTECTED } }; +inline constexpr sigtab::Sig notBenevolentUnionResults = { { 3668 /* notBenevolentUnionResults */, 1, 245, 1, 246, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig unionTypes = { { 3702 /* unionTypes */, 1, 247, 1, 248, ZEND_ACC_PROTECTED } }; +inline constexpr sigtab::Sig pickFromTypes = { { 3732 /* pickFromTypes */, 2, 249, 2, 251, ZEND_ACC_PROTECTED } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3785 /* toPhpDocNode */, 0, 252, 0, 252, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3839 /* toNativePhpTypeNode */, 1, 253, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig notBenevolentPickFromTypes = { { 3859 /* notBenevolentPickFromTypes */, 1, 254, 1, 255, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3886 /* hasTemplateOrLateResolvableType */, 0, 256, 0, 256, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::UnionType diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index 40460b50e2d..c8985a3dfef 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -1049,6 +1049,7 @@ public function enterNode(\PhpParser\Node $node) $covered[\PHPStan\Type\FloatType::class] = true; $covered[\PHPStan\Type\Constant\ConstantFloatType::class] = true; $covered[\PHPStan\Type\FloatRangeType::class] = true; +$covered[\PHPStan\Type\NumberSet::class] = true; $covered[\PHPStan\Type\NullType::class] = true; $covered[\PHPStan\Type\VoidType::class] = true; $covered[\PHPStan\Type\NeverType::class] = true; diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index a5a670cf654..56e6a690784 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -1039,6 +1039,16 @@ public function getValue() $r["$name getAlwaysLooselyEqualType $vi"] = $view($operand->getAlwaysLooselyEqualType($phpVersion)); } } + // the NumberSet each type describes its numbers with, and one built by hand + $numberSetOperands = $looseEqualityOperands + [ + 'intsAndNan' => new \PHPStan\Type\UnionType([\PHPStan\Type\IntegerRangeType::fromInterval(null, 2), new \PHPStan\Type\Constant\ConstantFloatType(NAN), new \PHPStan\Type\Constant\ConstantIntegerType(7)]), + ]; + foreach ($numberSetOperands as $name => $operand) { + $numberSet = $operand->toNumberSet(); + $r["$name toNumberSet"] = $numberSet === null ? null : [$numberSet->getIntegerIntervals(), $numberSet->getFloatIntervals(), $numberSet->hasNan()]; + } + $numberSet = new \PHPStan\Type\NumberSet([[null, -1], [3, 3]], [[-INF, -0.0], [1.5, 1.5]], true); + $r['new NumberSet'] = [$numberSet->getIntegerIntervals(), $numberSet->getFloatIntervals(), $numberSet->hasNan()]; foreach ($arithmeticOperands as $leftName => $left) { foreach ($arithmeticOperands as $rightName => $right) { From e0ecd7a1ed64142807f049612f96699d8fc13b52 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 19:20:27 +0000 Subject: [PATCH 2/4] Compute the numeric operators on NumberSet +, -, *, /, << and >> now run as NumberSet methods on the operands' toNumberSet(), pair by pair: integer pairs keep the integer rules, every other pair uses the float interval corners. This replaces the interval and float-range special cases in OperatorTypeHelper, and float members of integer unions are no longer passed through. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 5 - src/Type/NumberSet.php | 602 ++++++++++- src/Type/OperatorTypeHelper.php | 685 +------------ tests/PHPStan/Analyser/nsrt/binary.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8803.php | 4 +- .../nsrt/division-by-possible-zero.php | 4 +- .../Analyser/nsrt/float-range-arithmetic.php | 13 + .../Analyser/nsrt/integer-range-types.php | 2 +- .../loop-generalize-written-variables.php | 2 +- tests/PHPStan/Analyser/nsrt/math.php | 10 +- .../Analyser/nsrt/multiply-by-zero.php | 12 +- tests/PHPStan/Analyser/nsrt/strval.php | 2 +- turbo-ext/src/IetrSupport.h | 5 + turbo-ext/src/NumberSet.cpp | 638 +++++++++++- turbo-ext/src/OperatorTypeHelper.cpp | 939 +----------------- turbo-ext/src/generated/NumberSet.h | 121 ++- turbo-ext/src/generated/OperatorTypeHelper.h | 262 ++--- 17 files changed, 1559 insertions(+), 1749 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1653116f7de..9e58664acfc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1513,11 +1513,6 @@ parameters: count: 1 path: src/Type/OperatorTypeHelper.php - - - rawMessage: PHPDoc tag @var with type float|int|null is not subtype of type int|null. - identifier: varTag.type - count: 6 - path: src/Type/OperatorTypeHelper.php - rawMessage: Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead. diff --git a/src/Type/NumberSet.php b/src/Type/NumberSet.php index df13584430e..5b32aa4a798 100644 --- a/src/Type/NumberSet.php +++ b/src/Type/NumberSet.php @@ -2,22 +2,48 @@ namespace PHPStan\Type; +use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Turbo\ShadowedByTurboExtension; +use PHPStan\Type\Constant\ConstantFloatType; +use PHPStan\Type\Constant\ConstantIntegerType; +use function count; +use function in_array; +use function intdiv; +use function is_finite; +use function is_float; +use function is_int; +use function is_nan; +use function max; +use function min; +use const INF; +use const NAN; +use const PHP_FLOAT_MAX; +use const PHP_INT_MAX; +use const PHP_INT_MIN; /** - * The numbers a type holds, as Type::toNumberSet() describes them, for interval - * arithmetic that needs no knowledge of the Type classes. + * The numbers a type holds, as Type::toNumberSet() describes them, and the + * arithmetic of the operators on them. * * Integer intervals are closed, with null for an unbounded side. Float intervals * are closed in PHP's ordering of the doubles, where ±INF are values and both * zeros are one point (see FloatRangeType). NAN is in no interval. Each interval * stands for one member of the type, so a constant is an interval of one point * and intervals of different members can touch. + * + * An operator is computed for each pair of members on its own: two integer + * intervals by the integer rules, anything else as floats at the corners of the + * intervals, since a rounded IEEE operation is monotonic in each operand on + * either side of zero. */ #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/NumberSet.cpp')] final class NumberSet { + /** The denormal next to zero, the divisor of smallest magnitude. */ + private const SMALLEST_POSITIVE_FLOAT = 4.9E-324; + /** * @param list $integerIntervals * @param list $floatIntervals @@ -51,4 +77,576 @@ public function hasNan(): bool return $this->nan; } + /** Models the + operator. */ + public function add(self $right, PhpVersion $phpVersion): Type + { + return $this->calculate('+', $right, $phpVersion); + } + + /** Models the - operator. */ + public function subtract(self $right, PhpVersion $phpVersion): Type + { + return $this->calculate('-', $right, $phpVersion); + } + + /** Models the * operator. */ + public function multiply(self $right, PhpVersion $phpVersion): Type + { + return $this->calculate('*', $right, $phpVersion); + } + + /** + * Models the / operator. A zero divisor throws on PHP 8, so it is left out; + * on PHP 7 it gives ±INF, or NAN for a zero numerator. + */ + public function divide(self $right, PhpVersion $phpVersion): Type + { + return $this->calculate('/', $right, $phpVersion); + } + + /** Models the << operator; a negative shift is an error. */ + public function shiftLeft(self $right, PhpVersion $phpVersion): Type + { + return $this->calculate('<<', $right, $phpVersion); + } + + /** Models the >> operator; a negative shift is an error. */ + public function shiftRight(self $right, PhpVersion $phpVersion): Type + { + return $this->calculate('>>', $right, $phpVersion); + } + + /** + * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator + */ + private function calculate(string $operator, self $right, PhpVersion $phpVersion): Type + { + $left = $this; + if ($left->countMembers() * $right->countMembers() > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + $left = $left->toHull(); + $right = $right->toHull(); + } + + $isShift = $operator === '<<' || $operator === '>>'; + $integerTypes = []; + $floatTypes = []; + foreach ($left->integerIntervals as $leftInterval) { + foreach ($right->integerIntervals as $rightInterval) { + if ($operator === '/') { + [$integerType, $floatType] = self::divideIntegers($leftInterval, $rightInterval, $phpVersion); + $integerTypes[] = $integerType; + $floatTypes[] = $floatType; + continue; + } + + $integerTypes[] = self::calculateIntegers($operator, $leftInterval, $rightInterval); + } + if ($isShift || $right->floatIntervals === []) { + continue; + } + + $leftFloats = self::integerToFloatInterval($leftInterval); + foreach ($right->floatIntervals as [$rightMin, $rightMax]) { + $floatTypes[] = self::calculateFloats($operator, $leftFloats, [$rightMin, $rightMax, true], $phpVersion); + } + } + foreach ($left->floatIntervals as [$leftMin, $leftMax]) { + if ($isShift) { + break; + } + + foreach ($right->integerIntervals as $rightInterval) { + $rightFloats = self::integerToFloatInterval($rightInterval); + foreach ($operator === '/' ? self::splitIntegerDivisor($rightFloats) : [$rightFloats] as $divisor) { + $floatTypes[] = self::calculateFloats($operator, [$leftMin, $leftMax, true], $divisor, $phpVersion); + } + } + foreach ($right->floatIntervals as [$rightMin, $rightMax]) { + $floatTypes[] = self::calculateFloats($operator, [$leftMin, $leftMax, true], [$rightMin, $rightMax, true], $phpVersion); + } + } + + $leftHasFloats = $left->floatIntervals !== [] || $left->nan; + $rightHasFloats = $right->floatIntervals !== [] || $right->nan; + if ($isShift) { + // a float operand is converted to an integer first + if (($leftHasFloats && !$right->isEmpty()) || ($rightHasFloats && !$left->isEmpty())) { + $integerTypes[] = new IntegerType(); + } + + return TypeCombinator::union(...$integerTypes); + } + + if (($left->nan && !$right->isEmpty()) || ($right->nan && !$left->isEmpty())) { + $floatTypes[] = new ConstantFloatType(NAN); + } + + // any float but NAN reads better as float when no operand narrowed its floats + $floatType = TypeCombinator::union(...$floatTypes); + if (!$left->hasFloatRange() && !$right->hasFloatRange() && $floatType->equals(FloatRangeType::fromInterval(-INF, INF))) { + $floatType = new FloatType(); + } + + $integerType = TypeCombinator::union(...$integerTypes); + $resultType = TypeCombinator::union($integerType, $floatType); + if ( + $operator === '/' + && !$leftHasFloats + && !$rightHasFloats + && ($left->hasAnyInteger() || $right->hasAnyInteger() || $integerType->equals(new IntegerType())) + ) { + // the quotients of any integer are any number, which stays lenient + return TypeUtils::toBenevolentUnion($resultType); + } + + return $resultType; + } + + /** Whether a member is int, unbounded on both sides. */ + private function hasAnyInteger(): bool + { + foreach ($this->integerIntervals as [$min, $max]) { + if ($min === null && $max === null) { + return true; + } + } + + return false; + } + + private function countMembers(): int + { + return count($this->integerIntervals) + count($this->floatIntervals) + ($this->nan ? 1 : 0); + } + + private function isEmpty(): bool + { + return $this->integerIntervals === [] && $this->floatIntervals === [] && !$this->nan; + } + + /** Whether a float member is narrower than float and more than one value. */ + private function hasFloatRange(): bool + { + foreach ($this->floatIntervals as [$min, $max]) { + if ($min >= $max || ($this->nan && $min === -INF && $max === INF)) { + continue; + } + + return true; + } + + return false; + } + + /** One integer interval and one float interval that hold every member. */ + private function toHull(): self + { + $integerIntervals = []; + if ($this->integerIntervals !== []) { + [$min, $max] = $this->integerIntervals[0]; + foreach ($this->integerIntervals as [$innerMin, $innerMax]) { + $min = $min === null || $innerMin === null ? null : min($min, $innerMin); + $max = $max === null || $innerMax === null ? null : max($max, $innerMax); + } + $integerIntervals[] = [$min, $max]; + } + + $floatIntervals = []; + if ($this->floatIntervals !== []) { + [$min, $max] = $this->floatIntervals[0]; + foreach ($this->floatIntervals as [$innerMin, $innerMax]) { + $min = min($min, $innerMin); + $max = max($max, $innerMax); + } + $floatIntervals[] = [$min, $max]; + } + + return new self($integerIntervals, $floatIntervals, $this->nan); + } + + /** + * The floats an integer interval stands for. An unbounded side reaches the + * infinity on that side, which the integer never is. + * + * @param array{int|null, int|null} $interval + * @return array{float, float, bool} [min, max, whether an infinite bound is a value] + */ + private static function integerToFloatInterval(array $interval): array + { + [$min, $max] = $interval; + + return [$min === null ? -INF : (float) $min, $max === null ? INF : (float) $max, false]; + } + + /** + * An integer divisor next to zero is -1 or 1, not a denormal: the + * negative part, zero on its own and the positive part. + * + * @param array{float, float, bool} $interval + * @return list + */ + private static function splitIntegerDivisor(array $interval): array + { + [$min, $max] = $interval; + if ($min > 0.0 || $max < 0.0) { + return [$interval]; + } + + $intervals = []; + if ($min < 0.0) { + $intervals[] = [$min, -1.0, false]; + } + $intervals[] = [0.0, 0.0, false]; + if ($max > 0.0) { + $intervals[] = [1.0, $max, false]; + } + + return $intervals; + } + + /** + * Two integer points give the exact result; an interval bound that leaves + * the integer range becomes unbounded. + * + * @param '+'|'-'|'*'|'<<'|'>>' $operator + * @param array{int|null, int|null} $left + * @param array{int|null, int|null} $right + */ + private static function calculateIntegers(string $operator, array $left, array $right): Type + { + [$leftMin, $leftMax] = $left; + [$rightMin, $rightMax] = $right; + + if (in_array($operator, ['<<', '>>'], true)) { + if ($rightMin === null || $rightMin !== $rightMax) { + return new IntegerType(); + } + if ($rightMin < 0) { + return new ErrorType(); + } + if ($operator === '<<') { + // an overflowing shift wraps around, which breaks the monotonicity the bounds rely on; + // open bounds still include the platform's minimum or maximum integer + if ( + self::shiftLeftOverflows($leftMin ?? PHP_INT_MIN, $rightMin) + || self::shiftLeftOverflows($leftMax ?? PHP_INT_MAX, $rightMin) + ) { + return new IntegerType(); + } + + return IntegerRangeType::fromInterval( + $leftMin !== null ? $leftMin << $rightMin : null, + $leftMax !== null ? $leftMax << $rightMin : null, + ); + } + + return IntegerRangeType::fromInterval( + $leftMin !== null ? $leftMin >> $rightMin : null, + $leftMax !== null ? $leftMax >> $rightMin : null, + ); + } + + if ($leftMin !== null && $leftMin === $leftMax && $rightMin !== null && $rightMin === $rightMax) { + if ($operator === '+') { + $value = $leftMin + $rightMin; + } elseif ($operator === '-') { + $value = $leftMin - $rightMin; + } else { + $value = $leftMin * $rightMin; + } + + return ConstantTypeHelper::getTypeFromValue($value); + } + + if ($operator === '+') { + $min = $leftMin !== null && $rightMin !== null ? $leftMin + $rightMin : null; + $max = $leftMax !== null && $rightMax !== null ? $leftMax + $rightMax : null; + } elseif ($operator === '-') { + // [a, b] - [c, d] is [a - d, b - c] + $min = $leftMin !== null && $rightMax !== null ? $leftMin - $rightMax : null; + $max = $leftMax !== null && $rightMin !== null ? $leftMax - $rightMin : null; + } else { + $corners = [ + $leftMin === 0 || $rightMin === 0 ? 0 : ($leftMin ?? -INF) * ($rightMin ?? -INF), + $leftMin === 0 || $rightMax === 0 ? 0 : ($leftMin ?? -INF) * ($rightMax ?? INF), + $leftMax === 0 || $rightMin === 0 ? 0 : ($leftMax ?? INF) * ($rightMin ?? -INF), + $leftMax === 0 || $rightMax === 0 ? 0 : ($leftMax ?? INF) * ($rightMax ?? INF), + ]; + $min = min($corners); + $max = max($corners); + } + + return IntegerRangeType::fromInterval(is_int($min) ? $min : null, is_int($max) ? $max : null); + } + + /** + * A left shift that does not fit into an integer wraps around instead of turning + * into a float, so the shifted value no longer preserves the ordering of its operand. + */ + private static function shiftLeftOverflows(int $value, int $shift): bool + { + return ($value << $shift) >> $shift !== $value; + } + + /** + * The integers and the floats the quotients of two integer intervals are: + * the integers between the quotients of the bounds, which division is + * monotonic in as long as the divisor keeps its sign, and the floats there. + * + * @param array{int|null, int|null} $left + * @param array{int|null, int|null} $right + * @return array{Type, Type} + */ + private static function divideIntegers(array $left, array $right, PhpVersion $phpVersion): array + { + [$leftMin, $leftMax] = $left; + [$rightMin, $rightMax] = $right; + + if ($leftMin !== null && $leftMin === $leftMax && $rightMin !== null && $rightMin === $rightMax && $rightMin !== 0) { + $value = $leftMin / $rightMin; + if (is_int($value)) { + return [new ConstantIntegerType($value), new NeverType()]; + } + + return [new NeverType(), new ConstantFloatType($value)]; + } + + $integerTypes = []; + $floatTypes = []; + $divisors = []; + if ($rightMin === null || $rightMin < 0) { + $divisors[] = [$rightMin, $rightMax === null ? -1 : min($rightMax, -1)]; + } + if ($rightMax === null || $rightMax > 0) { + $divisors[] = [$rightMin === null ? 1 : max($rightMin, 1), $rightMax]; + } + foreach ($divisors as [$divisorMin, $divisorMax]) { + if ($divisorMin !== null && $divisorMax !== null && $divisorMin > $divisorMax) { + continue; + } + + $minimums = []; + $maximums = []; + foreach ([$leftMin ?? -INF, $leftMax ?? INF] as $numerator) { + foreach ([$divisorMin ?? -INF, $divisorMax ?? INF] as $divisor) { + [$floor, $ceil] = self::divideBound($numerator, $divisor); + $minimums[] = $ceil; + $maximums[] = $floor; + } + } + $integerTypes[] = IntegerRangeType::fromInterval(self::toIntBound(min($minimums)), self::toIntBound(max($maximums))); + $floatTypes[] = self::calculateFloats('/', self::integerToFloatInterval($left), self::integerToFloatInterval([$divisorMin, $divisorMax]), $phpVersion); + } + + $hasZeroDivisor = ($rightMin === null || $rightMin <= 0) && ($rightMax === null || $rightMax >= 0); + if ($hasZeroDivisor && !$phpVersion->throwsOnDivisionByZero()) { + if ($leftMax === null || $leftMax > 0) { + $floatTypes[] = new ConstantFloatType(INF); + } + if ($leftMin === null || $leftMin < 0) { + $floatTypes[] = new ConstantFloatType(-INF); + } + if (($leftMin === null || $leftMin <= 0) && ($leftMax === null || $leftMax >= 0)) { + $floatTypes[] = new ConstantFloatType(NAN); + } + } + + return [TypeCombinator::union(...$integerTypes), TypeCombinator::union(...$floatTypes)]; + } + + /** + * The integers next to the quotient of two bounds, [floor, ceil], each an + * infinity when the quotient is. An infinite divisor stands for the + * divisors growing without a bound, whose quotients approach zero from the + * side of their sign without reaching it. + * + * @return array{int|float, int|float} + */ + private static function divideBound(int|float $numerator, int|float $divisor): array + { + if (is_float($divisor)) { + if ($numerator === 0) { + return [0, 0]; + } + + return ($numerator > 0) === ($divisor > 0) ? [0, 1] : [-1, 0]; + } + + if (is_float($numerator)) { + $infinity = ($numerator > 0) === ($divisor > 0) ? INF : -INF; + + return [$infinity, $infinity]; + } + + if ($numerator === PHP_INT_MIN && $divisor === -1) { + // the quotient is the float right past the integer range + return [PHP_INT_MAX, INF]; + } + + $quotient = intdiv($numerator, $divisor); + if ($quotient * $divisor === $numerator) { + return [$quotient, $quotient]; + } + + // intdiv() truncates towards zero + if (($numerator < 0) !== ($divisor < 0)) { + return [$quotient - 1, $quotient]; + } + + return [$quotient, $quotient + 1]; + } + + /** + * The bound an overflowing float stands for: an int operation whose result + * leaves the int range yields a float there, so the integer part of the + * result reaches no further than the int range does. Casting the float + * instead would wrap the bound around (and warns since PHP 8.5): note that + * PHP_INT_MAX is not representable as a float, so the first float past the + * int range is (float) PHP_INT_MAX itself. An infinite bound is no bound. + */ + private static function toIntBound(int|float $value): ?int + { + if (is_int($value)) { + return $value; + } + if (!is_finite($value)) { + return null; + } + if ($value >= (float) PHP_INT_MAX) { + return PHP_INT_MAX; + } + if ($value <= (float) PHP_INT_MIN) { + return PHP_INT_MIN; + } + + return (int) $value; + } + + /** + * @param '+'|'-'|'*'|'/' $operator + * @param array{float, float, bool} $left [min, max, whether an infinite bound is a value] + * @param array{float, float, bool} $right + */ + private static function calculateFloats(string $operator, array $left, array $right, PhpVersion $phpVersion): Type + { + [$leftMin, $leftMax, $leftInfinities] = $left; + [$rightMin, $rightMax, $rightInfinities] = $right; + $resultTypes = []; + + $leftHasZero = $leftMin <= 0.0 && $leftMax >= 0.0; + $rightHasZero = $rightMin <= 0.0 && $rightMax >= 0.0; + $leftHasNegativeInfinity = $leftInfinities && $leftMin === -INF; + $leftHasPositiveInfinity = $leftInfinities && $leftMax === INF; + $rightHasNegativeInfinity = $rightInfinities && $rightMin === -INF; + $rightHasPositiveInfinity = $rightInfinities && $rightMax === INF; + $leftHasInfinity = $leftHasNegativeInfinity || $leftHasPositiveInfinity; + $rightHasInfinity = $rightHasNegativeInfinity || $rightHasPositiveInfinity; + + $rightIntervals = [[$rightMin, $rightMax]]; + if ($operator === '+') { + $nan = ($leftHasPositiveInfinity && $rightHasNegativeInfinity) || ($leftHasNegativeInfinity && $rightHasPositiveInfinity); + } elseif ($operator === '-') { + $nan = ($leftHasPositiveInfinity && $rightHasPositiveInfinity) || ($leftHasNegativeInfinity && $rightHasNegativeInfinity); + } elseif ($operator === '*') { + $nan = ($leftHasZero && $rightHasInfinity) || ($rightHasZero && $leftHasInfinity); + } else { + $nan = $leftHasInfinity && $rightHasInfinity; + if ($rightHasZero) { + // both zeros are divisors: x / 0.0 and x / -0.0 are opposite infinities + $rightIntervals = []; + if ($rightMin < 0.0) { + $rightIntervals[] = [$rightMin, -self::SMALLEST_POSITIVE_FLOAT]; + } + if ($rightMax > 0.0) { + $rightIntervals[] = [self::SMALLEST_POSITIVE_FLOAT, $rightMax]; + } + if (!$phpVersion->throwsOnDivisionByZero()) { + $nan = $nan || $leftHasZero; + if ($leftMin < 0.0 || $leftMax > 0.0) { + $resultTypes[] = FloatRangeType::fromInterval(-INF, INF); + } + } + } + } + + foreach ($rightIntervals as [$intervalMin, $intervalMax]) { + $corners = []; + foreach ([$leftMin, $leftMax] as $leftBound) { + foreach ([$intervalMin, $intervalMax] as $rightBound) { + $corner = self::calculateFloat($operator, $leftBound, $rightBound); + if (!is_nan($corner)) { + $corners[] = $corner; + continue; + } + + // the NAN of a corner is added above, the floats next to it bound the rest + foreach (self::getFloatBoundWithInnerNeighbour($leftBound, $leftMin, $leftMax) as $leftValue) { + foreach (self::getFloatBoundWithInnerNeighbour($rightBound, $intervalMin, $intervalMax) as $rightValue) { + $corner = self::calculateFloat($operator, $leftValue, $rightValue); + if (is_nan($corner)) { + continue; + } + $corners[] = $corner; + } + } + } + } + + if ($corners === []) { + continue; + } + + $resultTypes[] = FloatRangeType::fromInterval(min($corners), max($corners)); + } + + if ($nan) { + $resultTypes[] = new ConstantFloatType(NAN); + } + + return TypeCombinator::union(...$resultTypes); + } + + /** + * @param '+'|'-'|'*'|'/' $operator + */ + private static function calculateFloat(string $operator, float $left, float $right): float + { + if ($operator === '+') { + return $left + $right; + } + if ($operator === '-') { + return $left - $right; + } + if ($operator === '*') { + return $left * $right; + } + + return $left / $right; + } + + /** + * The bound and, when the interval goes on past an infinite or zero bound, + * the next float inside it: the values a NAN corner is the limit of. + * + * @return list + */ + private static function getFloatBoundWithInnerNeighbour(float $bound, float $min, float $max): array + { + if ($min === $max) { + return [$bound]; + } + + if ($bound === INF) { + return [$bound, PHP_FLOAT_MAX]; + } + if ($bound === -INF) { + return [$bound, -PHP_FLOAT_MAX]; + } + if ($bound === 0.0) { + return [$bound, $bound === $min ? self::SMALLEST_POSITIVE_FLOAT : -self::SMALLEST_POSITIVE_FLOAT]; + } + + return [$bound]; + } + } diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 56f829d2f09..fd7439adc42 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -22,25 +22,16 @@ use PHPStan\Type\Constant\ConstantStringType; use function abs; use function array_merge; -use function assert; -use function ceil; use function count; -use function floor; -use function get_class; use function in_array; use function intdiv; -use function intval; -use function is_finite; use function is_float; use function is_infinite; use function is_int; -use function is_nan; use function is_numeric; use function max; use function min; use const INF; -use const NAN; -use const PHP_FLOAT_MAX; use const PHP_INT_MAX; use const PHP_INT_MIN; @@ -55,9 +46,6 @@ final class OperatorTypeHelper private const IS_SCALAR_TYPE = 1; private const IS_UNKNOWN = 2; - /** The denormal next to zero, the divisor of smallest magnitude. */ - private const SMALLEST_POSITIVE_FLOAT = 4.9E-324; - public static function concat(Type $left, Type $right, PhpVersion $phpVersion): Type { $leftStringType = $left->toString(); @@ -421,12 +409,7 @@ private static function getFiniteOrConstantScalarTypes(Type $leftType, Type $rig public static function divide(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { - $floatRangeResult = self::floatRangeMath('/', $leftType, $rightType, $phpVersion); - if ($floatRangeResult !== null) { - return $floatRangeResult; - } - - $result = self::getDivTypeFromTypes($leftType, $rightType); + $result = self::getDivTypeFromTypes($leftType, $rightType, $phpVersion); if ($leftType->isInteger()->yes() && $rightType->isInteger()->yes()) { $modType = self::modulo($leftType, $rightType, $phpVersion); @@ -439,68 +422,12 @@ public static function divide(Type $leftType, Type $rightType, PhpVersion $phpVe } } - // the float of an int division lies between the quotients of the bounds - $floatType = self::getIntegerDivisionFloatType($leftType, $rightType, $phpVersion); - if ($floatType !== null) { - return self::replaceFloatType($result, $floatType); - } } return $result; } - private static function getIntegerDivisionFloatType(Type $leftType, Type $rightType, PhpVersion $phpVersion): ?Type - { - $leftMembers = self::getFloatIntervalMembers($leftType); - $rightMembers = self::getFloatIntervalMembers($rightType); - if ($leftMembers === null || $rightMembers === null) { - return null; - } - if (count($leftMembers) * count($rightMembers) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { - return null; - } - - $floatTypes = []; - foreach ($leftMembers as [, $leftInterval]) { - foreach ($rightMembers as [, $rightInterval]) { - foreach (self::splitIntegerDivisor($rightInterval) as $divisorInterval) { - $floatTypes[] = self::floatIntervalMath('/', $leftInterval, $divisorInterval, $phpVersion); - } - } - } - - // a quotient that can be any float but NAN reads better as float, like int + 1.5 does - $floatType = TypeCombinator::union(...$floatTypes); - if ($floatType->equals(FloatRangeType::fromInterval(-INF, INF))) { - return null; - } - - return $floatType; - } - - private static function replaceFloatType(Type $type, Type $floatType): Type - { - if (get_class($type) === FloatType::class) { - return $floatType; - } - if (!$type instanceof UnionType) { - return $type; - } - - $types = []; - foreach ($type->getTypes() as $innerType) { - $types[] = get_class($innerType) === FloatType::class ? $floatType : $innerType; - } - - $union = TypeCombinator::union(...$types); - if ($type instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($union); - } - - return $union; - } - - private static function getDivTypeFromTypes(Type $leftType, Type $rightType): Type + private static function getDivTypeFromTypes(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { $leftTypes = $leftType->getConstantScalarTypes(); $rightTypes = $rightType->getConstantScalarTypes(); @@ -545,7 +472,7 @@ private static function getDivTypeFromTypes(Type $leftType, Type $rightType): Ty } } - return self::resolveCommonMath('/', $leftType, $rightType); + return self::resolveCommonMath('/', $leftType, $rightType, $phpVersion); } public static function modulo(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type @@ -725,11 +652,6 @@ private static function getIntegerIntervals(Type $type): ?array public static function add(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { - $floatRangeResult = self::floatRangeMath('+', $leftType, $rightType, $phpVersion); - if ($floatRangeResult !== null) { - return $floatRangeResult; - } - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { return self::getNeverType($leftType, $rightType); } @@ -830,7 +752,7 @@ public static function add(Type $leftType, Type $rightType, PhpVersion $phpVersi } } - return self::resolveCommonMath('+', $leftType, $rightType); + return self::resolveCommonMath('+', $leftType, $rightType, $phpVersion); } public static function arrayUnion(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type @@ -978,11 +900,6 @@ private static function tryArrayUnion(Type $leftType, Type $rightType): ?Type public static function subtract(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { - $floatRangeResult = self::floatRangeMath('-', $leftType, $rightType, $phpVersion); - if ($floatRangeResult !== null) { - return $floatRangeResult; - } - $leftTypes = $leftType->getConstantScalarTypes(); $rightTypes = $rightType->getConstantScalarTypes(); $leftTypesCount = count($leftTypes); @@ -1016,16 +933,11 @@ public static function subtract(Type $leftType, Type $rightType, PhpVersion $php $rightType = self::optimizeScalarType($rightType); } - return self::resolveCommonMath('-', $leftType, $rightType); + return self::resolveCommonMath('-', $leftType, $rightType, $phpVersion); } public static function multiply(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { - $floatRangeResult = self::floatRangeMath('*', $leftType, $rightType, $phpVersion); - if ($floatRangeResult !== null) { - return $floatRangeResult; - } - $leftTypes = $leftType->getConstantScalarTypes(); $rightTypes = $rightType->getConstantScalarTypes(); $leftTypesCount = count($leftTypes); @@ -1069,7 +981,7 @@ public static function multiply(Type $leftType, Type $rightType, PhpVersion $php return new ConstantIntegerType(0); } - return self::resolveCommonMath('*', $leftType, $rightType); + return self::resolveCommonMath('*', $leftType, $rightType, $phpVersion); } public static function shiftLeft(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type @@ -1123,7 +1035,7 @@ public static function shiftLeft(Type $leftType, Type $rightType, PhpVersion $ph return new ErrorType(); } - return self::resolveCommonMath('<<', $leftType, $rightType); + return self::resolveCommonMath('<<', $leftType, $rightType, $phpVersion); } public static function shiftRight(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type @@ -1177,7 +1089,7 @@ public static function shiftRight(Type $leftType, Type $rightType, PhpVersion $p return new ErrorType(); } - return self::resolveCommonMath('>>', $leftType, $rightType); + return self::resolveCommonMath('>>', $leftType, $rightType, $phpVersion); } private static function optimizeScalarType(Type $type): Type @@ -1292,44 +1204,43 @@ private static function allBitsMask(int $value): int /** * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator */ - private static function resolveCommonMath(string $operator, Type $leftType, Type $rightType): Type + /** + * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator + */ + private static function resolveCommonMath(string $operator, Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { $types = TypeCombinator::union($leftType, $rightType); $leftNumberType = $leftType->toNumber(); $rightNumberType = $rightType->toNumber(); - if ( - !$types instanceof MixedType - && ( - $rightNumberType instanceof IntegerRangeType - || $rightNumberType instanceof ConstantIntegerType - || $rightNumberType instanceof UnionType - ) - ) { - if ($leftNumberType instanceof IntegerRangeType || $leftNumberType instanceof ConstantIntegerType) { - return self::integerRangeMath( - $leftNumberType, - $operator, - $rightNumberType, - ); - } elseif ($leftNumberType instanceof UnionType) { - $unionParts = []; - - foreach ($leftNumberType->getTypes() as $type) { - $numberType = $type->toNumber(); - if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) { - $unionParts[] = self::integerRangeMath($numberType, $operator, $rightNumberType); - } else { - $unionParts[] = $numberType; - } + if (!$types instanceof MixedType) { + $leftNumberSet = $leftNumberType->toNumberSet(); + $rightNumberSet = $rightNumberType->toNumberSet(); + if ($leftNumberSet !== null && $rightNumberSet !== null) { + if ($operator === '+') { + $result = $leftNumberSet->add($rightNumberSet, $phpVersion); + } elseif ($operator === '-') { + $result = $leftNumberSet->subtract($rightNumberSet, $phpVersion); + } elseif ($operator === '*') { + $result = $leftNumberSet->multiply($rightNumberSet, $phpVersion); + } elseif ($operator === '/') { + $result = $leftNumberSet->divide($rightNumberSet, $phpVersion); + } elseif ($operator === '<<') { + $result = $leftNumberSet->shiftLeft($rightNumberSet, $phpVersion); + } else { + $result = $leftNumberSet->shiftRight($rightNumberSet, $phpVersion); } - $union = TypeCombinator::union(...$unionParts); - if ($leftNumberType instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($union)->toNumber(); + if ( + $leftNumberType instanceof BenevolentUnionType + || $rightNumberType instanceof BenevolentUnionType + || $leftType instanceof BenevolentUnionType + || $rightType instanceof BenevolentUnionType + ) { + return TypeUtils::toBenevolentUnion($result); } - return $union->toNumber(); + return $result; } } @@ -1377,230 +1288,6 @@ private static function resolveCommonMath(string $operator, Type $leftType, Type return $resultType; } - /** - * @param ConstantIntegerType|IntegerRangeType $range - * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator - */ - private static function integerRangeMath(Type $range, string $operator, Type $operand): Type - { - if ($range instanceof IntegerRangeType) { - $rangeMin = $range->getMin(); - $rangeMax = $range->getMax(); - } else { - $rangeMin = $range->getValue(); - $rangeMax = $rangeMin; - } - - if ($operand instanceof UnionType) { - - $unionParts = []; - - foreach ($operand->getTypes() as $type) { - $numberType = $type->toNumber(); - if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) { - $unionParts[] = self::integerRangeMath($range, $operator, $numberType); - } else { - $unionParts[] = $type->toNumber(); - } - } - - $union = TypeCombinator::union(...$unionParts); - if ($operand instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($union)->toNumber(); - } - - return $union->toNumber(); - } - - $operand = $operand->toNumber(); - if ($operand instanceof IntegerRangeType) { - $operandMin = $operand->getMin(); - $operandMax = $operand->getMax(); - } elseif ($operand instanceof ConstantIntegerType) { - $operandMin = $operand->getValue(); - $operandMax = $operand->getValue(); - } else { - return $operand; - } - - if ($operator === '+') { - if ($operand instanceof ConstantIntegerType) { - /** @var int|float|null $min */ - $min = $rangeMin !== null ? $rangeMin + $operand->getValue() : null; - - /** @var int|float|null $max */ - $max = $rangeMax !== null ? $rangeMax + $operand->getValue() : null; - } else { - /** @var int|float|null $min */ - $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin + $operand->getMin() : null; - - /** @var int|float|null $max */ - $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null; - } - } elseif ($operator === '-') { - // [a, b] - [c, d] is [a - d, b - c] - /** @var int|float|null $min */ - $min = $rangeMin !== null && $operandMax !== null ? $rangeMin - $operandMax : null; - - /** @var int|float|null $max */ - $max = $rangeMax !== null && $operandMin !== null ? $rangeMax - $operandMin : null; - } elseif ($operator === '*') { - $min1 = $rangeMin === 0 || $operandMin === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMin ?? -INF); - $min2 = $rangeMin === 0 || $operandMax === 0 ? 0 : ($rangeMin ?? -INF) * ($operandMax ?? INF); - $max1 = $rangeMax === 0 || $operandMin === 0 ? 0 : ($rangeMax ?? INF) * ($operandMin ?? -INF); - $max2 = $rangeMax === 0 || $operandMax === 0 ? 0 : ($rangeMax ?? INF) * ($operandMax ?? INF); - - $min = min($min1, $min2, $max1, $max2); - $max = max($min1, $min2, $max1, $max2); - - if (!is_finite($min)) { - $min = null; - } - if (!is_finite($max)) { - $max = null; - } - } elseif ($operator === '/') { - if ($operand instanceof ConstantIntegerType) { - $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null; - $max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null; - } else { - // Avoid division by zero when looking for the min and the max by using the closest int - $operandMin = $operandMin !== 0 ? $operandMin : 1; - $operandMax = $operandMax !== 0 ? $operandMax : -1; - - if ( - ($operandMin < 0 || $operandMin === null) - && ($operandMax > 0 || $operandMax === null) - ) { - $negativeOperand = IntegerRangeType::fromInterval($operandMin, 0); - assert($negativeOperand instanceof IntegerRangeType); - $positiveOperand = IntegerRangeType::fromInterval(0, $operandMax); - assert($positiveOperand instanceof IntegerRangeType); - - $result = TypeCombinator::union( - self::integerRangeMath($range, $operator, $negativeOperand), - self::integerRangeMath($range, $operator, $positiveOperand), - )->toNumber(); - - if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) { - return new BenevolentUnionType([new IntegerType(), new FloatType()]); - } - - return $result; - } - if ( - ($rangeMin < 0 || $rangeMin === null) - && ($rangeMax > 0 || $rangeMax === null) - ) { - $negativeRange = IntegerRangeType::fromInterval($rangeMin, 0); - assert($negativeRange instanceof IntegerRangeType); - $positiveRange = IntegerRangeType::fromInterval(0, $rangeMax); - assert($positiveRange instanceof IntegerRangeType); - - $result = TypeCombinator::union( - self::integerRangeMath($negativeRange, $operator, $operand), - self::integerRangeMath($positiveRange, $operator, $operand), - )->toNumber(); - - if ($result->equals(new UnionType([new IntegerType(), new FloatType()]))) { - return new BenevolentUnionType([new IntegerType(), new FloatType()]); - } - - return $result; - } - - $rangeMinSign = ($rangeMin ?? -INF) <=> 0; - $rangeMaxSign = ($rangeMax ?? INF) <=> 0; - - $min1 = $operandMin !== null ? ($rangeMin ?? -INF) / $operandMin : $rangeMinSign * -0.1; - $min2 = $operandMax !== null ? ($rangeMin ?? -INF) / $operandMax : $rangeMinSign * 0.1; - $max1 = $operandMin !== null ? ($rangeMax ?? INF) / $operandMin : $rangeMaxSign * -0.1; - $max2 = $operandMax !== null ? ($rangeMax ?? INF) / $operandMax : $rangeMaxSign * 0.1; - - $min = min($min1, $min2, $max1, $max2); - $max = max($min1, $min2, $max1, $max2); - - if ($min === -INF) { - $min = null; - } - if ($max === INF) { - $max = null; - } - } - - if ($min !== null && $max !== null && $min > $max) { - [$min, $max] = [$max, $min]; - } - - if (is_float($min)) { - $min = self::toIntBound(ceil($min)); - } - if (is_float($max)) { - $max = self::toIntBound(floor($max)); - } - - // invert maximas on division with negative constants - if ((($range instanceof ConstantIntegerType && $range->getValue() < 0) - || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0)) - && ($min === null || $max === null)) { - [$min, $max] = [$max, $min]; - } - - if ($min === null && $max === null) { - return new BenevolentUnionType([new IntegerType(), new FloatType()]); - } - - return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType()); - } elseif ($operator === '<<') { - if (!$operand instanceof ConstantIntegerType) { - return new IntegerType(); - } - if ($operand->getValue() < 0) { - return new ErrorType(); - } - // an overflowing shift wraps around, which breaks the monotonicity the bounds rely on - // open bounds still include the platform's minimum or maximum integer - if ( - self::shiftLeftOverflows($rangeMin ?? PHP_INT_MIN, $operand->getValue()) - || self::shiftLeftOverflows($rangeMax ?? PHP_INT_MAX, $operand->getValue()) - ) { - return new IntegerType(); - } - - $min = $rangeMin !== null ? intval($rangeMin) << $operand->getValue() : null; - $max = $rangeMax !== null ? intval($rangeMax) << $operand->getValue() : null; - } elseif ($operator === '>>') { - if (!$operand instanceof ConstantIntegerType) { - return new IntegerType(); - } - if ($operand->getValue() < 0) { - return new ErrorType(); - } - $min = $rangeMin !== null ? intval($rangeMin) >> $operand->getValue() : null; - $max = $rangeMax !== null ? intval($rangeMax) >> $operand->getValue() : null; - } else { - throw new ShouldNotHappenException(); - } - - if (is_float($min)) { - $min = null; - } - if (is_float($max)) { - $max = null; - } - - return IntegerRangeType::fromInterval($min, $max); - } - - /** - * A left shift that does not fit into an integer wraps around instead of turning - * into a float, so the shifted value no longer preserves the ordering of its operand. - */ - private static function shiftLeftOverflows(int $value, int $shift): bool - { - return ($value << $shift) >> $shift !== $value; - } - private static function negateNumber(Type $type): Type { $type = $type->toNumber(); @@ -1629,29 +1316,6 @@ private static function negateNumber(Type $type): Type return $type; } - /** - * The bound an overflowing float stands for: an int operation whose result - * leaves the int range yields a float there, so the integer part of the - * result reaches no further than the int range does. Casting the float - * instead would wrap the bound around (and warns since PHP 8.5): note that - * PHP_INT_MAX is not representable as a float, so the first float past the - * int range is (float) PHP_INT_MAX itself. - */ - private static function toIntBound(float $value): ?int - { - if (!is_finite($value)) { - return null; - } - if ($value >= (float) PHP_INT_MAX) { - return PHP_INT_MAX; - } - if ($value <= (float) PHP_INT_MIN) { - return PHP_INT_MIN; - } - - return (int) $value; - } - /** * The integer a constant operand of %, <<, >>, &, | and ^ becomes: numeric * strings out of the int range saturate like an (int) cast instead of @@ -1675,283 +1339,6 @@ private static function getTypeFromValue($value): Type return ConstantTypeHelper::getTypeFromValue($value); } - /** - * Interval arithmetic for operands that hold a float range. Each pair of - * their members is computed on its own: two integers by the integer - * operators, anything else as floats at the corners of the intervals, - * since a rounded IEEE operation is monotonic in each operand on either - * side of zero. The NAN a pair can yield is added separately. - * - * @param '+'|'-'|'*'|'/' $operator - */ - private static function floatRangeMath(string $operator, Type $leftType, Type $rightType, PhpVersion $phpVersion): ?Type - { - $leftMembers = self::getFloatIntervalMembers($leftType); - $rightMembers = self::getFloatIntervalMembers($rightType); - if ($leftMembers === null || $rightMembers === null) { - return null; - } - - $hasFloatRange = false; - foreach ([$leftMembers, $rightMembers] as $members) { - foreach ($members as [$integerInterval, [$min, $max, $nan]]) { - // a float member that is neither a constant nor plain float - if ($integerInterval !== null || $min >= $max || $nan) { - continue; - } - - $hasFloatRange = true; - } - } - if (!$hasFloatRange) { - return null; - } - - if ($operator === '/') { - foreach ($rightMembers as [, [$min, $max]]) { - if ($min === 0.0 && $max === 0.0) { - return null; - } - } - } - - if (count($leftMembers) * count($rightMembers) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { - if ($leftType->isFloat()->yes() || $rightType->isFloat()->yes()) { - return new FloatType(); - } - - return new UnionType([new IntegerType(), new FloatType()]); - } - - $resultTypes = []; - foreach ($leftMembers as [$leftInteger, $leftInterval]) { - foreach ($rightMembers as [$rightInteger, $rightInterval]) { - if ($leftInteger !== null && $rightInteger !== null) { - $leftMember = IntegerRangeType::fromInterval($leftInteger[0], $leftInteger[1]); - $rightMember = IntegerRangeType::fromInterval($rightInteger[0], $rightInteger[1]); - if ($operator === '+') { - $resultTypes[] = self::add($leftMember, $rightMember, $phpVersion); - } elseif ($operator === '-') { - $resultTypes[] = self::subtract($leftMember, $rightMember, $phpVersion); - } elseif ($operator === '*') { - $resultTypes[] = self::multiply($leftMember, $rightMember, $phpVersion); - } else { - $resultTypes[] = self::divide($leftMember, $rightMember, $phpVersion); - } - continue; - } - - foreach ($operator === '/' && $rightInteger !== null ? self::splitIntegerDivisor($rightInterval) : [$rightInterval] as $divisorInterval) { - $resultTypes[] = self::floatIntervalMath($operator, $leftInterval, $divisorInterval, $phpVersion); - } - } - } - - return TypeCombinator::union(...$resultTypes); - } - - /** - * An integer divisor next to zero is -1 or 1, not a denormal: the - * negative part, zero on its own and the positive part. - * - * @param array{float, float, bool, bool} $interval - * @return list - */ - private static function splitIntegerDivisor(array $interval): array - { - [$min, $max] = $interval; - if ($min > 0.0 || $max < 0.0) { - return [$interval]; - } - - $intervals = []; - if ($min < 0.0) { - $intervals[] = [$min, -1.0, false, false]; - } - $intervals[] = [0.0, 0.0, false, false]; - if ($max > 0.0) { - $intervals[] = [1.0, $max, false, false]; - } - - return $intervals; - } - - /** - * The members of a number type, each with the integers it is (null for a - * float member) and the floats it stands for: [min, max, whether NAN, - * whether an infinite bound is a value]. An int without a bound reaches the - * infinity on that side, which it never is, and NAN alone is an empty interval. - * - * @return list|null - */ - private static function getFloatIntervalMembers(Type $type): ?array - { - if ($type instanceof BenevolentUnionType) { - return null; - } - - $numberSet = $type->toNumberSet(); - if ($numberSet === null) { - return null; - } - - $members = []; - foreach ($numberSet->getIntegerIntervals() as $integerInterval) { - [$min, $max] = $integerInterval; - $members[] = [$integerInterval, [$min === null ? -INF : (float) $min, $max === null ? INF : (float) $max, false, false]]; - } - // plain float is one member: the full interval and NAN - $plainFloat = self::isPlainFloat($numberSet); - foreach ($numberSet->getFloatIntervals() as [$min, $max]) { - $members[] = [null, [$min, $max, $plainFloat, true]]; - } - if ($numberSet->hasNan() && !$plainFloat) { - $members[] = [null, [INF, -INF, true, true]]; - } - - return $members; - } - - /** - * Whether the floats of the set are all of them, which a union holds as one - * FloatType member. - */ - private static function isPlainFloat(NumberSet $numberSet): bool - { - return $numberSet->hasNan() && $numberSet->getFloatIntervals() === [[-INF, INF]]; - } - - /** - * @param '+'|'-'|'*'|'/' $operator - * @param array{float, float, bool, bool} $left - * @param array{float, float, bool, bool} $right - */ - private static function floatIntervalMath(string $operator, array $left, array $right, PhpVersion $phpVersion): Type - { - [$leftMin, $leftMax, $nan, $leftInfinities] = $left; - [$rightMin, $rightMax, $rightNan, $rightInfinities] = $right; - $nan = $nan || $rightNan; - $resultTypes = []; - - if ($leftMin <= $leftMax && $rightMin <= $rightMax) { - $leftHasZero = $leftMin <= 0.0 && $leftMax >= 0.0; - $rightHasZero = $rightMin <= 0.0 && $rightMax >= 0.0; - // the infinities that stand for an unbounded int are not values - $leftHasNegativeInfinity = $leftInfinities && $leftMin === -INF; - $leftHasPositiveInfinity = $leftInfinities && $leftMax === INF; - $rightHasNegativeInfinity = $rightInfinities && $rightMin === -INF; - $rightHasPositiveInfinity = $rightInfinities && $rightMax === INF; - $leftHasInfinity = $leftHasNegativeInfinity || $leftHasPositiveInfinity; - $rightHasInfinity = $rightHasNegativeInfinity || $rightHasPositiveInfinity; - - $rightIntervals = [[$rightMin, $rightMax]]; - if ($operator === '+') { - $nan = $nan || ($leftHasPositiveInfinity && $rightHasNegativeInfinity) || ($leftHasNegativeInfinity && $rightHasPositiveInfinity); - } elseif ($operator === '-') { - $nan = $nan || ($leftHasPositiveInfinity && $rightHasPositiveInfinity) || ($leftHasNegativeInfinity && $rightHasNegativeInfinity); - } elseif ($operator === '*') { - $nan = $nan || ($leftHasZero && $rightHasInfinity) || ($rightHasZero && $leftHasInfinity); - } else { - $nan = $nan || ($leftHasInfinity && $rightHasInfinity); - if ($rightHasZero) { - // both zeros are divisors: x / 0.0 and x / -0.0 are opposite infinities - $rightIntervals = []; - if ($rightMin < 0.0) { - $rightIntervals[] = [$rightMin, -self::SMALLEST_POSITIVE_FLOAT]; - } - if ($rightMax > 0.0) { - $rightIntervals[] = [self::SMALLEST_POSITIVE_FLOAT, $rightMax]; - } - if (!$phpVersion->throwsOnDivisionByZero()) { - $nan = $nan || $leftHasZero; - if ($leftMin < 0.0 || $leftMax > 0.0) { - $resultTypes[] = FloatRangeType::fromInterval(-INF, INF); - } - } - } - } - - foreach ($rightIntervals as [$intervalMin, $intervalMax]) { - $corners = []; - foreach ([$leftMin, $leftMax] as $leftBound) { - foreach ([$intervalMin, $intervalMax] as $rightBound) { - $corner = self::calculateFloat($operator, $leftBound, $rightBound); - if (!is_nan($corner)) { - $corners[] = $corner; - continue; - } - - // the NAN of a corner is added above, the floats next to it bound the rest - foreach (self::getFloatBoundWithInnerNeighbour($leftBound, $leftMin, $leftMax) as $leftValue) { - foreach (self::getFloatBoundWithInnerNeighbour($rightBound, $intervalMin, $intervalMax) as $rightValue) { - $corner = self::calculateFloat($operator, $leftValue, $rightValue); - if (is_nan($corner)) { - continue; - } - $corners[] = $corner; - } - } - } - } - - if ($corners === []) { - continue; - } - - $resultTypes[] = FloatRangeType::fromInterval(min($corners), max($corners)); - } - } - - if ($nan) { - $resultTypes[] = new ConstantFloatType(NAN); - } - - return TypeCombinator::union(...$resultTypes); - } - - /** - * @param '+'|'-'|'*'|'/' $operator - */ - private static function calculateFloat(string $operator, float $left, float $right): float - { - if ($operator === '+') { - return $left + $right; - } - if ($operator === '-') { - return $left - $right; - } - if ($operator === '*') { - return $left * $right; - } - - return $left / $right; - } - - /** - * The bound and, when the interval goes on past an infinite or zero bound, - * the next float inside it: the values a NAN corner is the limit of. - * - * @return list - */ - private static function getFloatBoundWithInnerNeighbour(float $bound, float $min, float $max): array - { - if ($min === $max) { - return [$bound]; - } - - if ($bound === INF) { - return [$bound, PHP_FLOAT_MAX]; - } - if ($bound === -INF) { - return [$bound, -PHP_FLOAT_MAX]; - } - if ($bound === 0.0) { - return [$bound, $bound === $min ? self::SMALLEST_POSITIVE_FLOAT : -self::SMALLEST_POSITIVE_FLOAT]; - } - - return [$bound]; - } - private static function getNeverType(Type $leftType, Type $rightType): Type { // make sure we don't lose the explicit flag in the process diff --git a/tests/PHPStan/Analyser/nsrt/binary.php b/tests/PHPStan/Analyser/nsrt/binary.php index 5d598177e1e..d1d7f29ffac 100644 --- a/tests/PHPStan/Analyser/nsrt/binary.php +++ b/tests/PHPStan/Analyser/nsrt/binary.php @@ -329,7 +329,7 @@ public function doFoo(array $generalArray) assertType('float', $float + $float); assertType('float', $float + $number); assertType('(float|int)', 1 / $mixed); - assertType('float|int', 1 / $number); + assertType('-1|1|float', 1 / $number); assertType('float', 1.0 / $mixed); assertType('float', 1.0 / $number); assertType('(float|int)', $mixed / 1); diff --git a/tests/PHPStan/Analyser/nsrt/bug-8803.php b/tests/PHPStan/Analyser/nsrt/bug-8803.php index 88af4df14d0..433fe79deae 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8803.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8803.php @@ -22,8 +22,8 @@ public function sayHello(): void assertType('int<1, 14>', $offset); assertType('int<2, 21>', $value); assertType('int<2, 21>', $value2); - assertType('float', $value3); - assertType('float', $value4); + assertType('float<1001.0, 1014.0>', $value3); + assertType('float<1001.0, 1014.0>', $value4); } } diff --git a/tests/PHPStan/Analyser/nsrt/division-by-possible-zero.php b/tests/PHPStan/Analyser/nsrt/division-by-possible-zero.php index b557cfe9636..9b985c5567c 100644 --- a/tests/PHPStan/Analyser/nsrt/division-by-possible-zero.php +++ b/tests/PHPStan/Analyser/nsrt/division-by-possible-zero.php @@ -22,8 +22,8 @@ function doFoo(int $range1, int $range2, int $int, int $r1, int $i, int $rMin, i assertType('float<0.0, 5.0>|int<1, 5>', 5 / $range1); assertType('float<-5.0, 0.0>|int<-5, -1>', 5 / $range2); assertType('float|int', $range1 / $range2); - assertType('(float<-5.0, 5.0>|int)', 5 / $int); - assertType('(float<-10.0, 10.0>|int)', $r1 / $i); + assertType('(float<-5.0, 5.0>|int<-5, -1>|int<1, 5>)', 5 / $int); + assertType('(float<-10.0, 10.0>|int<-10, -1>|int<1, 10>)', $r1 / $i); assertType('float<-10.0, 0.0>|float<0.2, 10.0>|int<-10, -1>|int<1, 10>', $r1 / $rMin); assertType('float<0.0, max>|int<0, max>', $a / $b); assertType('-1|1|float<-1.0, -0.2>|float<0.0, 1.0>', -1 / $rMin); diff --git a/tests/PHPStan/Analyser/nsrt/float-range-arithmetic.php b/tests/PHPStan/Analyser/nsrt/float-range-arithmetic.php index 889c2a3c590..b3b0272507e 100644 --- a/tests/PHPStan/Analyser/nsrt/float-range-arithmetic.php +++ b/tests/PHPStan/Analyser/nsrt/float-range-arithmetic.php @@ -58,6 +58,19 @@ public function doFoo(float $a, float $b, float $p, float $nn, float $fin, int $ assertType('float<1.5, 2.5>', $x); } + /** + * @param int<1, 5> $r + * @param 1.5|2 $u + * @param 1.5|int<1, 2> $v + */ + public function floatMembersOfIntegerUnions(int $r, $u, $v): void + { + assertType('float<2.5, 6.5>|int<3, 7>', $r + $u); + assertType('float<1.5, 7.5>|int<1, 10>', $r * $v); + assertType('float<-3.5, 0.5>|int<-4, 1>', $v - $r); + assertType('int', $v << 1); + } + /** * @param float<1, 2> $a * @param float<-3.5, 4.5> $b diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index aed83355318..fcbd7757141 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -311,7 +311,7 @@ public function maximaInversion($rMin, $rMax) { assertType('float<-0.4, 0.0>', -2 / $rMax); assertType('int<-5, max>', $rMin / -1); - assertType('float|int', $rMax / -2); + assertType('float|int', $rMax / -2); } /** diff --git a/tests/PHPStan/Analyser/nsrt/loop-generalize-written-variables.php b/tests/PHPStan/Analyser/nsrt/loop-generalize-written-variables.php index b14995a586d..6a18de6fba3 100644 --- a/tests/PHPStan/Analyser/nsrt/loop-generalize-written-variables.php +++ b/tests/PHPStan/Analyser/nsrt/loop-generalize-written-variables.php @@ -29,7 +29,7 @@ function doWhileNarrowedOnFirstPass(int $x0, int $xn): void function forLoop(int $x0, int $xn): void { for ($xi = $x0; $xi < $xn; $xi += 0.1) { - assertType('int<1, max>', $xn); + assertType('int<2, max>', $xn); } assertType('int<1, max>', $xn); } diff --git a/tests/PHPStan/Analyser/nsrt/math.php b/tests/PHPStan/Analyser/nsrt/math.php index a53bcbcba16..98d34d3d55b 100644 --- a/tests/PHPStan/Analyser/nsrt/math.php +++ b/tests/PHPStan/Analyser/nsrt/math.php @@ -115,26 +115,26 @@ public function doSit(int $i, int $j): void public function multiplyZero(int $i, float $f, $range): void { assertType('0', $i * false); - assertType('float', $f * false); + assertType('0.0|NAN', $f * false); assertType('0', $range * false); assertType('0', $i * '0'); - assertType('float', $f * '0'); + assertType('0.0|NAN', $f * '0'); assertType('0', $range * '0'); assertType('0', $i * 0); - assertType('float', $f * 0); + assertType('0.0|NAN', $f * 0); assertType('0', $range * 0); assertType('0', 0 * $i); - assertType('float', 0 * $f); + assertType('0.0|NAN', 0 * $f); assertType('0', 0 * $range); $i *= 0; $f *= 0; $range *= 0; assertType('0', $i); - assertType('float', $f); + assertType('0.0|NAN', $f); assertType('0', $range); } diff --git a/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php b/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php index 3e56bd69acc..a3ff277cca5 100644 --- a/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php +++ b/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php @@ -10,12 +10,12 @@ function doFoo(int $int, float $float, int|float $intOrFloat, string $numericString, bool $bool): void { // 0 * 1.5 is 0.0, 0 * -1.5 is -0.0 and 0 * INF is NAN - assertType('float', 0 * $float); - assertType('float', $float * 0); - assertType('float', null * $float); - assertType('float|int', 0 * $intOrFloat); - assertType('float|int', null * $numericString); - assertType('float|int', $numericString * false); + assertType('0.0|NAN', 0 * $float); + assertType('0.0|NAN', $float * 0); + assertType('0.0|NAN', null * $float); + assertType('0|0.0|NAN', 0 * $intOrFloat); + assertType('0|0.0|NAN', null * $numericString); + assertType('0|0.0|NAN', $numericString * false); // an integer times zero is always the integer 0 assertType('0', 0 * $int); diff --git a/tests/PHPStan/Analyser/nsrt/strval.php b/tests/PHPStan/Analyser/nsrt/strval.php index 2cb3686a6ab..847e106ab18 100644 --- a/tests/PHPStan/Analyser/nsrt/strval.php +++ b/tests/PHPStan/Analyser/nsrt/strval.php @@ -60,7 +60,7 @@ function floatvalTest(string $string): void assertType('0.0|1.0', floatval(rand(0, 1) === 0)); assertType('42.0', floatval(42)); assertType('float<0.0, 9.223372036854776E+18>', floatval(rand())); - assertType('float', floatval(rand() * 0.1)); + assertType('float<0.0, max>', floatval(rand() * 0.1)); assertType('0.0', floatval([])); assertType('1.0', floatval([null])); assertType('float', floatval(new \stdClass())); diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h index 788d5c6c9f5..7ae6d97e218 100644 --- a/turbo-ext/src/IetrSupport.h +++ b/turbo-ext/src/IetrSupport.h @@ -668,4 +668,9 @@ inline void setNullableLong(zval *out, NullableLong value) } // namespace phpstanturbo::ietr +/* the arithmetic of NumberSet::add() & co. (NumberSet.cpp) on the sets' + * data: `kind` IETR_PLUS, _MINUS, _MUL, _DIV, _SHIFT_LEFT or _SHIFT_RIGHT; + * UNDEF = pending exception */ +zv::Val pt_number_set_calculate(phpstanturbo::ietr::IetrBinaryKind kind, const phpstanturbo::NumberSetData &left, const phpstanturbo::NumberSetData &right, zval *phpVersion); + #endif diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp index a68691b617e..57350981b07 100644 --- a/turbo-ext/src/NumberSet.cpp +++ b/turbo-ext/src/NumberSet.cpp @@ -8,12 +8,18 @@ * * pt_number_set_new() and pt_type_to_number_set() are the native producers' * and consumers' way in and out of the PHP arrays, so the operator helpers - * work on NumberSetData instead. + * work on NumberSetData instead. The arithmetic (NumberSetMath) works on that + * data too: the twin's private methods live only here. */ #include "TypeTraits.h" +#include "IetrSupport.h" #include "generated/NumberSet.h" +#include +#include +#include + namespace slots = ptdecl::NumberSet::slot; namespace sigs = ptdecl::NumberSet::sig; @@ -123,6 +129,609 @@ class NumberSet } }; + +/* {{{ the arithmetic of the operators */ + +using namespace phpstanturbo::ietr; + +/* Mirrors the private methods of the twin's arithmetic: calculate() and + * the helpers it pairs the members with. */ +class NumberSetMath +{ +public: + /* an integer bound of divideIntegers(): an int, or the twin's -INF / INF + * (inf -1 / 1) */ + struct Bound + { + int inf; + zend_long value; + + static Bound of(zend_long v) { return {0, v}; } + static Bound infinity(int sign) { return {sign, 0}; } + + bool positive() const { return inf != 0 ? inf > 0 : value > 0; } + }; + + /* [min, max, whether an infinite bound is a value] */ + struct FloatBounds + { + double min; + double max; + bool infinities; + }; + + /* The denormal next to zero, the divisor of smallest magnitude. */ + static constexpr double SMALLEST_POSITIVE_FLOAT = 4.9E-324; + + /* Mirrors calculate(). */ + static zv::Val calculate(IetrBinaryKind kind, const NumberSetData &leftIn, const NumberSetData &rightIn, zval *phpVersion) + { + NumberSetData left = leftIn; + NumberSetData right = rightIn; + if ((zend_long) countMembers(left) * (zend_long) countMembers(right) > PT_IETR_CALCULATE_SCALARS_LIMIT) { + left = toHull(left); + right = toHull(right); + } + + bool isShift = kind == IETR_SHIFT_LEFT || kind == IETR_SHIFT_RIGHT; + zv::Arr integerTypes = zv::Arr::create(4); + zv::Arr floatTypes = zv::Arr::create(4); + for (const NumberSetData::IntegerInterval &leftInterval : left.integerIntervals) { + for (const NumberSetData::IntegerInterval &rightInterval : right.integerIntervals) { + if (kind == IETR_DIV) { + zv::Val integerType, floatType; + if (UNEXPECTED(!divideIntegers(leftInterval, rightInterval, phpVersion, integerType, floatType))) return zv::Val(); + integerTypes.push(std::move(integerType)); + floatTypes.push(std::move(floatType)); + continue; + } + + IETR_VAL(integerType, calculateIntegers(kind, leftInterval, rightInterval)); + integerTypes.push(std::move(integerType)); + } + if (isShift || right.floatIntervals.empty()) continue; + + FloatBounds leftFloats = integerToFloatInterval(leftInterval); + for (const NumberSetData::FloatInterval &rightInterval : right.floatIntervals) { + IETR_VAL(floatType, calculateFloats(kind, leftFloats, {rightInterval.min, rightInterval.max, true}, phpVersion)); + floatTypes.push(std::move(floatType)); + } + } + for (const NumberSetData::FloatInterval &leftInterval : left.floatIntervals) { + if (isShift) break; + + FloatBounds leftFloats = {leftInterval.min, leftInterval.max, true}; + for (const NumberSetData::IntegerInterval &rightInterval : right.integerIntervals) { + FloatBounds rightFloats = integerToFloatInterval(rightInterval); + FloatBounds divisors[3]; + size_t divisorCount = 1; + divisors[0] = rightFloats; + if (kind == IETR_DIV) divisorCount = splitIntegerDivisor(rightFloats, divisors); + for (size_t i = 0; i < divisorCount; i++) { + IETR_VAL(floatType, calculateFloats(kind, leftFloats, divisors[i], phpVersion)); + floatTypes.push(std::move(floatType)); + } + } + for (const NumberSetData::FloatInterval &rightInterval : right.floatIntervals) { + IETR_VAL(floatType, calculateFloats(kind, leftFloats, {rightInterval.min, rightInterval.max, true}, phpVersion)); + floatTypes.push(std::move(floatType)); + } + } + + bool leftHasFloats = !left.floatIntervals.empty() || left.nan; + bool rightHasFloats = !right.floatIntervals.empty() || right.nan; + if (isShift) { + // a float operand is converted to an integer first + if ((leftHasFloats && !isEmpty(right)) || (rightHasFloats && !isEmpty(left))) { + IETR_VAL(integerType, newIntegerType()); + integerTypes.push(std::move(integerType)); + } + + return unionOf(integerTypes); + } + + if ((left.nan && !isEmpty(right)) || (right.nan && !isEmpty(left))) { + IETR_VAL(nanType, newConstantFloatType(std::numeric_limits::quiet_NaN())); + floatTypes.push(std::move(nanType)); + } + + // any float but NAN reads better as float when no operand narrowed its floats + IETR_VAL(floatType, unionOf(floatTypes)); + if (!hasFloatRange(left) && !hasFloatRange(right)) { + IETR_VAL(fullRange, pt_float_range_from_interval(-HUGE_VAL, HUGE_VAL)); + IETR_VAL(isFullRange, callOn(floatType.raw(), mEquals, 1, fullRange.raw())); + if (Z_TYPE_P(isFullRange.raw()) == IS_TRUE) { + floatType = newFloatType(); + if (UNEXPECTED(floatType.isUndef())) return zv::Val(); + } + } + + IETR_VAL(integerType, unionOf(integerTypes)); + IETR_VAL(resultType, union2(integerType.raw(), floatType.raw())); + if (kind == IETR_DIV && !leftHasFloats && !rightHasFloats) { + bool anyInteger = hasAnyInteger(left) || hasAnyInteger(right); + if (!anyInteger) { + IETR_VAL(plainInteger, newIntegerType()); + IETR_VAL(isPlainInteger, callOn(integerType.raw(), mEquals, 1, plainInteger.raw())); + anyInteger = Z_TYPE_P(isPlainInteger.raw()) == IS_TRUE; + } + if (anyInteger) { + // the quotients of any integer are any number, which stays lenient + return toBenevolentUnion(resultType.raw()); + } + } + + return resultType; + } + +private: + static size_t countMembers(const NumberSetData &set) + { + return set.integerIntervals.size() + set.floatIntervals.size() + (set.nan ? 1 : 0); + } + + static bool isEmpty(const NumberSetData &set) + { + return set.integerIntervals.empty() && set.floatIntervals.empty() && !set.nan; + } + + /* Mirrors hasFloatRange(). */ + static bool hasFloatRange(const NumberSetData &set) + { + for (const NumberSetData::FloatInterval &interval : set.floatIntervals) { + if (interval.min >= interval.max || (set.nan && interval.min == -HUGE_VAL && interval.max == HUGE_VAL)) continue; + return true; + } + return false; + } + + /* Mirrors hasAnyInteger(). */ + static bool hasAnyInteger(const NumberSetData &set) + { + for (const NumberSetData::IntegerInterval &interval : set.integerIntervals) { + if (interval.min.isNull && interval.max.isNull) return true; + } + return false; + } + + /* Mirrors toHull(); min() / max() keep the first of equal values. */ + static NumberSetData toHull(const NumberSetData &set) + { + NumberSetData hull; + if (!set.integerIntervals.empty()) { + NullableLong min = set.integerIntervals[0].min; + NullableLong max = set.integerIntervals[0].max; + for (const NumberSetData::IntegerInterval &interval : set.integerIntervals) { + min = min.isNull || interval.min.isNull ? NullableLong::null() : NullableLong::of(interval.min.value < min.value ? interval.min.value : min.value); + max = max.isNull || interval.max.isNull ? NullableLong::null() : NullableLong::of(interval.max.value > max.value ? interval.max.value : max.value); + } + hull.integerIntervals.push_back({min, max}); + } + if (!set.floatIntervals.empty()) { + double min = set.floatIntervals[0].min; + double max = set.floatIntervals[0].max; + for (const NumberSetData::FloatInterval &interval : set.floatIntervals) { + if (interval.min < min) min = interval.min; + if (interval.max > max) max = interval.max; + } + hull.floatIntervals.push_back({min, max}); + } + hull.nan = set.nan; + return hull; + } + + /* Mirrors integerToFloatInterval(). */ + static FloatBounds integerToFloatInterval(const NumberSetData::IntegerInterval &interval) + { + return { + interval.min.isNull ? -HUGE_VAL : static_cast(interval.min.value), + interval.max.isNull ? HUGE_VAL : static_cast(interval.max.value), + false, + }; + } + + /* Mirrors splitIntegerDivisor(): the count of intervals written */ + static size_t splitIntegerDivisor(FloatBounds interval, FloatBounds out[3]) + { + if (interval.min > 0.0 || interval.max < 0.0) { + out[0] = interval; + return 1; + } + + size_t count = 0; + if (interval.min < 0.0) out[count++] = {interval.min, -1.0, false}; + out[count++] = {0.0, 0.0, false}; + if (interval.max > 0.0) out[count++] = {1.0, interval.max, false}; + return count; + } + + /* $out = $a $b of two ints through the engine (overflowing into a + * float); false = pending exception */ + [[nodiscard]] static bool longOp(pt_ietr_binary_op fn, zend_long a, zend_long b, zval *out) + { + zval x, y; + ZVAL_LONG(&x, a); + ZVAL_LONG(&y, b); + return scalarOp(fn, &x, &y, out); + } + + /* is_int($value) ? $value : null of an int|float zval */ + static NullableLong intOrNull(const zval *value) + { + return Z_TYPE_P(value) == IS_LONG ? NullableLong::of(Z_LVAL_P(value)) : NullableLong::null(); + } + + /* Mirrors shiftLeftOverflows(): `($value << $shift) >> $shift !== $value` + * through the engine's operators; false = pending exception */ + [[nodiscard]] static bool shiftLeftOverflows(zend_long value, zend_long shift, bool &out) + { + zval shifted, restored, shiftZv; + ZVAL_LONG(&shiftZv, shift); + if (UNEXPECTED(!longOp(shift_left_function, value, shift, &shifted))) return false; + if (UNEXPECTED(!scalarOp(shift_right_function, &shifted, &shiftZv, &restored))) return false; + out = Z_TYPE(restored) != IS_LONG || Z_LVAL(restored) != value; + return true; + } + + /* Mirrors calculateIntegers(). */ + static zv::Val calculateIntegers(IetrBinaryKind kind, const NumberSetData::IntegerInterval &left, const NumberSetData::IntegerInterval &right) + { + NullableLong leftMin = left.min, leftMax = left.max; + NullableLong rightMin = right.min, rightMax = right.max; + + if (kind == IETR_SHIFT_LEFT || kind == IETR_SHIFT_RIGHT) { + if (rightMin.isNull || rightMax.isNull || rightMin.value != rightMax.value) return newIntegerType(); + zend_long shift = rightMin.value; + if (shift < 0) return newErrorType(); + pt_ietr_binary_op fn = kind == IETR_SHIFT_LEFT ? shift_left_function : shift_right_function; + if (kind == IETR_SHIFT_LEFT) { + // an overflowing shift wraps around, which breaks the monotonicity the bounds rely on; + // open bounds still include the platform's minimum or maximum integer + bool overflows = false; + if (UNEXPECTED(!shiftLeftOverflows(leftMin.isNull ? ZEND_LONG_MIN : leftMin.value, shift, overflows))) return zv::Val(); + if (!overflows && UNEXPECTED(!shiftLeftOverflows(leftMax.isNull ? ZEND_LONG_MAX : leftMax.value, shift, overflows))) return zv::Val(); + if (overflows) return newIntegerType(); + } + + zval min, max; + ZVAL_NULL(&min); + ZVAL_NULL(&max); + if (!leftMin.isNull && UNEXPECTED(!longOp(fn, leftMin.value, shift, &min))) return zv::Val(); + if (!leftMax.isNull && UNEXPECTED(!longOp(fn, leftMax.value, shift, &max))) return zv::Val(); + return fromIntervalZv(&min, &max); + } + + if (!leftMin.isNull && !leftMax.isNull && leftMin.value == leftMax.value && !rightMin.isNull && !rightMax.isNull && rightMin.value == rightMax.value) { + pt_ietr_binary_op fn = kind == IETR_PLUS ? add_function : (kind == IETR_MINUS ? sub_function : mul_function); + zval value; + if (UNEXPECTED(!longOp(fn, leftMin.value, rightMin.value, &value))) return zv::Val(); + return typeFromValue(&value); + } + + zval min, max; + ZVAL_NULL(&min); + ZVAL_NULL(&max); + if (kind == IETR_PLUS) { + if (!leftMin.isNull && !rightMin.isNull && UNEXPECTED(!longOp(add_function, leftMin.value, rightMin.value, &min))) return zv::Val(); + if (!leftMax.isNull && !rightMax.isNull && UNEXPECTED(!longOp(add_function, leftMax.value, rightMax.value, &max))) return zv::Val(); + } else if (kind == IETR_MINUS) { + // [a, b] - [c, d] is [a - d, b - c] + if (!leftMin.isNull && !rightMax.isNull && UNEXPECTED(!longOp(sub_function, leftMin.value, rightMax.value, &min))) return zv::Val(); + if (!leftMax.isNull && !rightMin.isNull && UNEXPECTED(!longOp(sub_function, leftMax.value, rightMin.value, &max))) return zv::Val(); + } else { + zval minusInf, inf; + ZVAL_DOUBLE(&minusInf, -HUGE_VAL); + ZVAL_DOUBLE(&inf, HUGE_VAL); + zval a, b, c, d; + setNullableLong(&a, leftMin); + setNullableLong(&b, leftMax); + setNullableLong(&c, rightMin); + setNullableLong(&d, rightMax); + zval corners[4]; + if (UNEXPECTED(!mulOrZero(&a, &c, &minusInf, &minusInf, &corners[0]))) return zv::Val(); + if (UNEXPECTED(!mulOrZero(&a, &d, &minusInf, &inf, &corners[1]))) return zv::Val(); + if (UNEXPECTED(!mulOrZero(&b, &c, &inf, &minusInf, &corners[2]))) return zv::Val(); + if (UNEXPECTED(!mulOrZero(&b, &d, &inf, &inf, &corners[3]))) return zv::Val(); + zv::Arr cornerList = zv::Arr::create(4); + for (zval &corner : corners) cornerList.push(zv::Val::adopt(corner)); + IETR_VAL(minValue, phpMinMax(false, 1, cornerList.raw())); + IETR_VAL(maxValue, phpMinMax(true, 1, cornerList.raw())); + ZVAL_COPY_VALUE(&min, minValue.raw()); + ZVAL_COPY_VALUE(&max, maxValue.raw()); + } + + return fromInterval(intOrNull(&min), intOrNull(&max)); + } + + /* Mirrors divideIntegers(); false = pending exception */ + [[nodiscard]] static bool divideIntegers(const NumberSetData::IntegerInterval &left, const NumberSetData::IntegerInterval &right, zval *phpVersion, zv::Val &integerTypeOut, zv::Val &floatTypeOut) + { + NullableLong leftMin = left.min, leftMax = left.max; + NullableLong rightMin = right.min, rightMax = right.max; + + if (!leftMin.isNull && !leftMax.isNull && leftMin.value == leftMax.value && !rightMin.isNull && !rightMax.isNull && rightMin.value == rightMax.value && rightMin.value != 0) { + zval value; + if (UNEXPECTED(!longOp(div_function, leftMin.value, rightMin.value, &value))) return false; + if (Z_TYPE(value) == IS_LONG) { + integerTypeOut = newConstantIntegerType(Z_LVAL(value)); + floatTypeOut = newNeverType(); + } else { + integerTypeOut = newNeverType(); + floatTypeOut = newConstantFloatType(Z_DVAL(value)); + } + return !integerTypeOut.isUndef() && !floatTypeOut.isUndef(); + } + + zv::Arr integerTypes = zv::Arr::create(2); + zv::Arr floatTypes = zv::Arr::create(5); + NumberSetData::IntegerInterval divisors[2]; + size_t divisorCount = 0; + if (rightMin.isNull || rightMin.value < 0) { + divisors[divisorCount++] = {rightMin, rightMax.isNull ? NullableLong::of(-1) : NullableLong::of(rightMax.value < -1 ? rightMax.value : -1)}; + } + if (rightMax.isNull || rightMax.value > 0) { + divisors[divisorCount++] = {rightMin.isNull ? NullableLong::of(1) : NullableLong::of(rightMin.value > 1 ? rightMin.value : 1), rightMax}; + } + for (size_t i = 0; i < divisorCount; i++) { + NullableLong divisorMin = divisors[i].min, divisorMax = divisors[i].max; + if (!divisorMin.isNull && !divisorMax.isNull && divisorMin.value > divisorMax.value) continue; + + bool hasExtremes = false; + Bound minimum = Bound::of(0), maximum = Bound::of(0); + Bound numerators[2] = {leftMin.isNull ? Bound::infinity(-1) : Bound::of(leftMin.value), leftMax.isNull ? Bound::infinity(1) : Bound::of(leftMax.value)}; + Bound divisorBounds[2] = {divisorMin.isNull ? Bound::infinity(-1) : Bound::of(divisorMin.value), divisorMax.isNull ? Bound::infinity(1) : Bound::of(divisorMax.value)}; + for (Bound numerator : numerators) { + for (Bound divisor : divisorBounds) { + Bound floor, ceil; + divideBound(numerator, divisor, floor, ceil); + // min() / max() keep the first of equal values + if (!hasExtremes) { + minimum = ceil; + maximum = floor; + hasExtremes = true; + continue; + } + if (lessThanBound(ceil, minimum)) minimum = ceil; + if (lessThanBound(maximum, floor)) maximum = floor; + } + } + zv::Val integerType = fromInterval(toIntBound(minimum), toIntBound(maximum)); + if (UNEXPECTED(integerType.isUndef())) return false; + integerTypes.push(std::move(integerType)); + zv::Val floatType = calculateFloats(IETR_DIV, integerToFloatInterval(left), integerToFloatInterval({divisorMin, divisorMax}), phpVersion); + if (UNEXPECTED(floatType.isUndef())) return false; + floatTypes.push(std::move(floatType)); + } + + bool hasZeroDivisor = (rightMin.isNull || rightMin.value <= 0) && (rightMax.isNull || rightMax.value >= 0); + if (hasZeroDivisor) { + bool throws; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_THROWS_ON_DIVISION_BY_ZERO, throws))) return false; + if (!throws) { + if (leftMax.isNull || leftMax.value > 0) { + zv::Val inf = newConstantFloatType(HUGE_VAL); + if (UNEXPECTED(inf.isUndef())) return false; + floatTypes.push(std::move(inf)); + } + if (leftMin.isNull || leftMin.value < 0) { + zv::Val minusInf = newConstantFloatType(-HUGE_VAL); + if (UNEXPECTED(minusInf.isUndef())) return false; + floatTypes.push(std::move(minusInf)); + } + if ((leftMin.isNull || leftMin.value <= 0) && (leftMax.isNull || leftMax.value >= 0)) { + zv::Val nan = newConstantFloatType(std::numeric_limits::quiet_NaN()); + if (UNEXPECTED(nan.isUndef())) return false; + floatTypes.push(std::move(nan)); + } + } + } + + integerTypeOut = unionOf(integerTypes); + if (UNEXPECTED(integerTypeOut.isUndef())) return false; + floatTypeOut = unionOf(floatTypes); + return !floatTypeOut.isUndef(); + } + + /* $a < $b of two int|±INF values */ + static bool lessThanBound(Bound a, Bound b) + { + if (a.inf != b.inf) return a.inf < b.inf; + if (a.inf != 0) return false; + return a.value < b.value; + } + + /* Mirrors divideBound(): [floor, ceil] */ + static void divideBound(Bound numerator, Bound divisor, Bound &floor, Bound &ceil) + { + if (divisor.inf != 0) { + if (numerator.inf == 0 && numerator.value == 0) { + floor = ceil = Bound::of(0); + return; + } + if (numerator.positive() == divisor.positive()) { + floor = Bound::of(0); + ceil = Bound::of(1); + } else { + floor = Bound::of(-1); + ceil = Bound::of(0); + } + return; + } + + if (numerator.inf != 0) { + floor = ceil = Bound::infinity(numerator.positive() == divisor.positive() ? 1 : -1); + return; + } + + if (numerator.value == ZEND_LONG_MIN && divisor.value == -1) { + // the quotient is the float right past the integer range + floor = Bound::of(ZEND_LONG_MAX); + ceil = Bound::infinity(1); + return; + } + + zend_long quotient = numerator.value / divisor.value; + if (quotient * divisor.value == numerator.value) { + floor = ceil = Bound::of(quotient); + return; + } + + // intdiv() truncates towards zero + if ((numerator.value < 0) != (divisor.value < 0)) { + floor = Bound::of(quotient - 1); + ceil = Bound::of(quotient); + return; + } + + floor = Bound::of(quotient); + ceil = Bound::of(quotient + 1); + } + + /* Mirrors toIntBound() of an int or ±INF: an infinity is no bound */ + static NullableLong toIntBound(Bound value) + { + return value.inf != 0 ? NullableLong::null() : NullableLong::of(value.value); + } + + /* Mirrors calculateFloats(). */ + static zv::Val calculateFloats(IetrBinaryKind kind, FloatBounds left, FloatBounds right, zval *phpVersion) + { + double leftMin = left.min, leftMax = left.max; + double rightMin = right.min, rightMax = right.max; + bool nan = false; + zv::Arr resultTypes = zv::Arr::create(4); + + bool leftHasZero = leftMin <= 0.0 && leftMax >= 0.0; + bool rightHasZero = rightMin <= 0.0 && rightMax >= 0.0; + bool leftHasNegativeInfinity = left.infinities && leftMin == -HUGE_VAL; + bool leftHasPositiveInfinity = left.infinities && leftMax == HUGE_VAL; + bool rightHasNegativeInfinity = right.infinities && rightMin == -HUGE_VAL; + bool rightHasPositiveInfinity = right.infinities && rightMax == HUGE_VAL; + bool leftHasInfinity = leftHasNegativeInfinity || leftHasPositiveInfinity; + bool rightHasInfinity = rightHasNegativeInfinity || rightHasPositiveInfinity; + + double rightIntervals[2][2] = {{rightMin, rightMax}, {0.0, 0.0}}; + size_t rightIntervalCount = 1; + if (kind == IETR_PLUS) { + nan = (leftHasPositiveInfinity && rightHasNegativeInfinity) || (leftHasNegativeInfinity && rightHasPositiveInfinity); + } else if (kind == IETR_MINUS) { + nan = (leftHasPositiveInfinity && rightHasPositiveInfinity) || (leftHasNegativeInfinity && rightHasNegativeInfinity); + } else if (kind == IETR_MUL) { + nan = (leftHasZero && rightHasInfinity) || (rightHasZero && leftHasInfinity); + } else { + nan = leftHasInfinity && rightHasInfinity; + if (rightHasZero) { + // both zeros are divisors: x / 0.0 and x / -0.0 are opposite infinities + rightIntervalCount = 0; + if (rightMin < 0.0) { + rightIntervals[rightIntervalCount][0] = rightMin; + rightIntervals[rightIntervalCount][1] = -SMALLEST_POSITIVE_FLOAT; + rightIntervalCount++; + } + if (rightMax > 0.0) { + rightIntervals[rightIntervalCount][0] = SMALLEST_POSITIVE_FLOAT; + rightIntervals[rightIntervalCount][1] = rightMax; + rightIntervalCount++; + } + bool throws; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_THROWS_ON_DIVISION_BY_ZERO, throws))) return zv::Val(); + if (!throws) { + nan = nan || leftHasZero; + if (leftMin < 0.0 || leftMax > 0.0) { + IETR_VAL(full, pt_float_range_from_interval(-HUGE_VAL, HUGE_VAL)); + resultTypes.push(std::move(full)); + } + } + } + } + + for (size_t i = 0; i < rightIntervalCount; i++) { + double intervalMin = rightIntervals[i][0]; + double intervalMax = rightIntervals[i][1]; + bool hasCorner = false; + double cornerMin = 0.0, cornerMax = 0.0; + auto addCorner = [&](double corner) { + if (!hasCorner) { + cornerMin = corner; + cornerMax = corner; + hasCorner = true; + return; + } + // min() / max() keep the first of equal values + if (corner < cornerMin) cornerMin = corner; + if (corner > cornerMax) cornerMax = corner; + }; + for (double leftBound : {leftMin, leftMax}) { + for (double rightBound : {intervalMin, intervalMax}) { + double corner = calculateFloat(kind, leftBound, rightBound); + if (!std::isnan(corner)) { + addCorner(corner); + continue; + } + + // the NAN of a corner is added above, the floats next to it bound the rest + double leftValues[2], rightValues[2]; + size_t leftCount = getFloatBoundWithInnerNeighbour(leftBound, leftMin, leftMax, leftValues); + size_t rightCount = getFloatBoundWithInnerNeighbour(rightBound, intervalMin, intervalMax, rightValues); + for (size_t l = 0; l < leftCount; l++) { + for (size_t r = 0; r < rightCount; r++) { + corner = calculateFloat(kind, leftValues[l], rightValues[r]); + if (std::isnan(corner)) continue; + addCorner(corner); + } + } + } + } + + if (!hasCorner) continue; + + IETR_VAL(range, pt_float_range_from_interval(cornerMin, cornerMax)); + resultTypes.push(std::move(range)); + } + + if (nan) { + IETR_VAL(nanType, newConstantFloatType(std::numeric_limits::quiet_NaN())); + resultTypes.push(std::move(nanType)); + } + + return unionOf(resultTypes); + } + + /* Mirrors calculateFloat(). */ + static double calculateFloat(IetrBinaryKind kind, double left, double right) + { + if (kind == IETR_PLUS) return left + right; + if (kind == IETR_MINUS) return left - right; + if (kind == IETR_MUL) return left * right; + + return left / right; + } + + /* Mirrors getFloatBoundWithInnerNeighbour(): the count of values written */ + static size_t getFloatBoundWithInnerNeighbour(double bound, double min, double max, double out[2]) + { + out[0] = bound; + if (min == max) return 1; + + if (bound == HUGE_VAL) { + out[1] = std::numeric_limits::max(); + return 2; + } + if (bound == -HUGE_VAL) { + out[1] = -std::numeric_limits::max(); + return 2; + } + if (bound == 0.0) { + out[1] = bound == min ? SMALLEST_POSITIVE_FLOAT : -SMALLEST_POSITIVE_FLOAT; + return 2; + } + + return 1; + } +}; + +/* }}} */ + } // namespace phpstanturbo using phpstanturbo::NumberSet; @@ -133,6 +742,11 @@ zv::Val pt_number_set_new(const NumberSetData &data) return NumberSet::create(data); } +zv::Val pt_number_set_calculate(phpstanturbo::ietr::IetrBinaryKind kind, const NumberSetData &left, const NumberSetData &right, zval *phpVersion) +{ + return phpstanturbo::NumberSetMath::calculate(kind, left, right, phpVersion); +} + bool pt_number_set_read(zend_object *numberSet, NumberSetData &out) { return NumberSet(numberSet).read(out); @@ -154,10 +768,26 @@ bool pt_type_to_number_set(zval *type, NumberSetData &out, bool &has) #define PT_THIS NumberSet(Z_OBJ_P(ZEND_THIS)) +/* add() & co.: (self $right, PhpVersion $phpVersion) */ +template +static void pt_ns_operator(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(right), pt_ce_number_set))) { + zend_type_error("%s::%s(): Argument #1 ($right) must be of type %s, %s given", ZSTR_VAL(pt_ce_number_set->name), get_active_function_name(), ZSTR_VAL(pt_ce_number_set->name), zend_zval_value_name(right)); + RETURN_THROWS(); + } + NumberSetData left, rightData; + if (UNEXPECTED(!PT_THIS.read(left) || !NumberSet(Z_OBJ_P(right)).read(rightData))) RETURN_THROWS(); + PT_RETURN_VAL(phpstanturbo::NumberSetMath::calculate(Kind, left, rightData, phpVersion)); +} + PT_MINIT_REGISTRATION(pt_register_number_set) { reg::Class cls("PHPStan\\Type\\NumberSet"); ptdecl::NumberSet::declareClass(cls); + cls.privateClassConstantValue("SMALLEST_POSITIVE_FLOAT", [](zval *out) { ZVAL_DOUBLE(out, phpstanturbo::NumberSetMath::SMALLEST_POSITIVE_FLOAT); }); /* "integerIntervals", "floatIntervals" and "nan" must stay the declared * properties in this order (slots::*) */ ptdecl::NumberSet::declareProperties(cls); @@ -180,6 +810,12 @@ PT_MINIT_REGISTRATION(pt_register_number_set) ZEND_PARSE_PARAMETERS_NONE(); PT_RETURN_VAL(PT_THIS.get(slots::nan, "nan")); }); + cls.method(sigs::add, pt_ns_operator); + cls.method(sigs::subtract, pt_ns_operator); + cls.method(sigs::multiply, pt_ns_operator); + cls.method(sigs::divide, pt_ns_operator); + cls.method(sigs::shiftLeft, pt_ns_operator); + cls.method(sigs::shiftRight, pt_ns_operator); cls.shadow(&pt_ce_number_set); } diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index a68cdc733d7..a95dfd96a58 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -569,10 +569,7 @@ class OperatorTypeHelper /* Mirrors divide(). */ static zv::Val divide(zval *leftType, zval *rightType, zval *phpVersion) { - IETR_VAL(floatRangeResult, floatRangeMath(IETR_DIV, leftType, rightType, phpVersion)); - if (!floatRangeResult.isNull()) return floatRangeResult; - - IETR_VAL(result, getDivTypeFromTypes(leftType, rightType)); + IETR_VAL(result, getDivTypeFromTypes(leftType, rightType, phpVersion)); IETR_TRI(leftIsInteger, triOn(leftType, mIsInteger)); if (leftIsInteger == PT_TRI_YES) { @@ -593,12 +590,6 @@ class OperatorTypeHelper } } } - - // the float of an int division lies between the quotients of the bounds - IETR_VAL(floatType, getIntegerDivisionFloatType(leftType, rightType, phpVersion)); - if (!floatType.isNull()) { - return replaceFloatType(result.raw(), floatType.raw()); - } } } @@ -606,7 +597,7 @@ class OperatorTypeHelper } /* Mirrors getDivTypeFromTypes(). */ - static zv::Val getDivTypeFromTypes(zval *leftType, zval *rightType) + static zv::Val getDivTypeFromTypes(zval *leftType, zval *rightType, zval *phpVersion) { zv::Val result; int constant = constantScalarArithmetic(leftType, rightType, IETR_DIV, result); @@ -635,7 +626,7 @@ class OperatorTypeHelper } } - return resolveCommonMath(IETR_DIV, leftType, rightType); + return resolveCommonMath(IETR_DIV, leftType, rightType, phpVersion); } /* Mirrors modulo(). */ @@ -747,23 +738,6 @@ class OperatorTypeHelper return fromInterval(rangeMin, rangeMax); } - /* Mirrors toIntBound(): the bound an overflowing float stands for, since - * the values past the int range are floats. ZEND_LONG_MAX is not - * representable as a double, so the first double past the int range is - * (double) ZEND_LONG_MAX itself. */ - static void toIntBound(double value, zval *out) - { - if (!std::isfinite(value)) { - ZVAL_NULL(out); - } else if (value >= (double) ZEND_LONG_MAX) { - ZVAL_LONG(out, ZEND_LONG_MAX); - } else if (value <= (double) ZEND_LONG_MIN) { - ZVAL_LONG(out, ZEND_LONG_MIN); - } else { - ZVAL_LONG(out, (zend_long) value); - } - } - /* private static: the highest possible absolute value of `$x % $divisor`, * one less than the largest possible absolute value of the divisor; null * when there is no such bound. A divisor reaching PHP_INT_MIN is reported @@ -873,21 +847,9 @@ class OperatorTypeHelper return zv::Val(std::move(pair)); } - /* shiftLeftOverflows() for the private method's glue; false = pending - * exception */ - [[nodiscard]] static bool shiftLeftOverflowsValue(zend_long value, zend_long shift, bool &out) - { - zval shiftZv; - ZVAL_LONG(&shiftZv, shift); - return shiftLeftOverflows(value, &shiftZv, out); - } - /* Mirrors add(). */ static zv::Val add(zval *leftType, zval *rightType, zval *phpVersion) { - IETR_VAL(floatRangeResult, floatRangeMath(IETR_PLUS, leftType, rightType, phpVersion)); - if (!floatRangeResult.isNull()) return floatRangeResult; - if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { return getNeverType(leftType, rightType); } @@ -998,7 +960,7 @@ class OperatorTypeHelper } } - return resolveCommonMath(IETR_PLUS, leftType, rightType); + return resolveCommonMath(IETR_PLUS, leftType, rightType, phpVersion); } /* Mirrors arrayUnion(). */ @@ -1207,9 +1169,6 @@ class OperatorTypeHelper /* Mirrors subtract() / multiply() (kind IETR_MINUS / IETR_MUL). */ static zv::Val subtractOrMultiply(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) { - IETR_VAL(floatRangeResult, floatRangeMath(kind, leftType, rightType, phpVersion)); - if (!floatRangeResult.isNull()) return floatRangeResult; - zv::Val result; int constant = constantScalarArithmetic(leftType, rightType, kind, result); if (UNEXPECTED(constant < 0)) return zv::Val(); @@ -1244,11 +1203,11 @@ class OperatorTypeHelper } } - return resolveCommonMath(kind, leftType, rightType); + return resolveCommonMath(kind, leftType, rightType, phpVersion); } /* Mirrors shiftLeft() / shiftRight(). */ - static zv::Val shift(IetrBinaryKind kind, zval *leftType, zval *rightType) + static zv::Val shift(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) { if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { return getNeverType(leftType, rightType); @@ -1279,7 +1238,7 @@ class OperatorTypeHelper } } - return resolveCommonMath(kind, leftType, rightType); + return resolveCommonMath(kind, leftType, rightType, phpVersion); } /* Mirrors optimizeScalarType(). */ @@ -1420,48 +1379,33 @@ class OperatorTypeHelper /* }}} */ - /* {{{ resolveCommonMath() / integerRangeMath() */ + /* {{{ resolveCommonMath() */ /* Mirrors resolveCommonMath() ($operator as `kind`). */ - static zv::Val resolveCommonMath(IetrBinaryKind kind, zval *leftType, zval *rightType) + static zv::Val resolveCommonMath(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) { IETR_VAL(types, union2(leftType, rightType)); IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); bool typesMixed = isA(types.raw(), pt_ce_mixed_type); - if ( - !typesMixed - && ( - isA(rightNumberType.raw(), pt_ce_integer_range_type) - || isA(rightNumberType.raw(), pt_ce_constant_integer_type) - || isA(rightNumberType.raw(), pt_ce_union_type) - ) - ) { - if (isA(leftNumberType.raw(), pt_ce_integer_range_type) || isA(leftNumberType.raw(), pt_ce_constant_integer_type)) { - return integerRangeMath(leftNumberType.raw(), kind, rightNumberType.raw()); - } else if (isA(leftNumberType.raw(), pt_ce_union_type)) { - IETR_VAL(leftTypes, callOn(leftNumberType.raw(), mGetTypes)); - if (UNEXPECTED(!requireArray(leftTypes.raw()))) return zv::Val(); - zv::Arr unionParts = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(leftTypes.raw()))); - - for (zv::ArrayEntry entry : zv::ArrRef(leftTypes.raw())) { - IETR_VAL(numberType, callOn(entry.value().raw(), mToNumber)); - if (isA(numberType.raw(), pt_ce_integer_range_type) || isA(numberType.raw(), pt_ce_constant_integer_type)) { - IETR_VAL(part, integerRangeMath(numberType.raw(), kind, rightNumberType.raw())); - unionParts.push(std::move(part)); - } else { - unionParts.push(std::move(numberType)); - } - } - - IETR_VAL(union_, unionOf(unionParts)); - if (isA(leftNumberType.raw(), pt_ce_benevolent_union_type)) { - IETR_VAL(benevolent, toBenevolentUnion(union_.raw())); - return callOn(benevolent.raw(), mToNumber); + if (!typesMixed) { + NumberSetData leftNumberSet, rightNumberSet; + bool leftHas = false, rightHas = false; + if (UNEXPECTED(!pt_type_to_number_set(leftNumberType.raw(), leftNumberSet, leftHas))) return zv::Val(); + if (UNEXPECTED(!pt_type_to_number_set(rightNumberType.raw(), rightNumberSet, rightHas))) return zv::Val(); + if (leftHas && rightHas) { + IETR_VAL(result, pt_number_set_calculate(kind, leftNumberSet, rightNumberSet, phpVersion)); + if ( + isA(leftNumberType.raw(), pt_ce_benevolent_union_type) + || isA(rightNumberType.raw(), pt_ce_benevolent_union_type) + || isA(leftType, pt_ce_benevolent_union_type) + || isA(rightType, pt_ce_benevolent_union_type) + ) { + return toBenevolentUnion(result.raw()); } - return callOn(union_.raw(), mToNumber); + return result; } } @@ -1516,297 +1460,6 @@ class OperatorTypeHelper return resultType; } - /* Mirrors integerRangeMath() ($range a ConstantIntegerType or - * IntegerRangeType, $operator as `kind`). */ - static zv::Val integerRangeMath(zval *range, IetrBinaryKind kind, zval *operandIn) - { - zval rangeMin, rangeMax; - if (isA(range, pt_ce_integer_range_type)) { - NullableLong min, max; - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(range), min, max))) return zv::Val(); - setNullableLong(&rangeMin, min); - setNullableLong(&rangeMax, max); - } else { - IETR_VAL(value, callOn(range, mGetValue)); - if (UNEXPECTED(Z_TYPE_P(value.raw()) != IS_LONG)) { - // the twin's `$range->getValue()` of a ConstantIntegerType is always an int - pt_throw_should_not_happen(); - return zv::Val(); - } - ZVAL_LONG(&rangeMin, Z_LVAL_P(value.raw())); - ZVAL_COPY_VALUE(&rangeMax, &rangeMin); - } - - if (isA(operandIn, pt_ce_union_type)) { - IETR_VAL(operandTypes, callOn(operandIn, mGetTypes)); - if (UNEXPECTED(!requireArray(operandTypes.raw()))) return zv::Val(); - zv::Arr unionParts = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(operandTypes.raw()))); - - for (zv::ArrayEntry entry : zv::ArrRef(operandTypes.raw())) { - zval *type = entry.value().raw(); - IETR_VAL(numberType, callOn(type, mToNumber)); - if (isA(numberType.raw(), pt_ce_integer_range_type) || isA(numberType.raw(), pt_ce_constant_integer_type)) { - IETR_VAL(part, integerRangeMath(range, kind, numberType.raw())); - unionParts.push(std::move(part)); - } else { - IETR_VAL(part, callOn(type, mToNumber)); - unionParts.push(std::move(part)); - } - } - - IETR_VAL(union_, unionOf(unionParts)); - if (isA(operandIn, pt_ce_benevolent_union_type)) { - IETR_VAL(benevolent, toBenevolentUnion(union_.raw())); - return callOn(benevolent.raw(), mToNumber); - } - - return callOn(union_.raw(), mToNumber); - } - - IETR_VAL(operand, callOn(operandIn, mToNumber)); - zval operandMin, operandMax; - bool operandIsConstant; - if (isA(operand.raw(), pt_ce_integer_range_type)) { - NullableLong min, max; - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(operand.raw()), min, max))) return zv::Val(); - setNullableLong(&operandMin, min); - setNullableLong(&operandMax, max); - operandIsConstant = false; - } else if (isA(operand.raw(), pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(operand.raw()), value))) return zv::Val(); - ZVAL_LONG(&operandMin, value); - ZVAL_LONG(&operandMax, value); - operandIsConstant = true; - } else { - return operand; - } - - // $operand->getValue() of a ConstantIntegerType operand - zval *operandValue = &operandMin; - - // the int|float|null results; floats and the INF sentinels are plain - // doubles, so every zval here is scalar and needs no release - zval min, max; - ZVAL_NULL(&min); - ZVAL_NULL(&max); - - zval zero; - ZVAL_LONG(&zero, 0); - - switch (kind) { - case IETR_PLUS: - if (operandIsConstant) { - if (Z_TYPE(rangeMin) != IS_NULL && UNEXPECTED(!scalarOp(add_function, &rangeMin, operandValue, &min))) return zv::Val(); - if (Z_TYPE(rangeMax) != IS_NULL && UNEXPECTED(!scalarOp(add_function, &rangeMax, operandValue, &max))) return zv::Val(); - } else { - if (Z_TYPE(rangeMin) != IS_NULL && Z_TYPE(operandMin) != IS_NULL && UNEXPECTED(!scalarOp(add_function, &rangeMin, &operandMin, &min))) return zv::Val(); - if (Z_TYPE(rangeMax) != IS_NULL && Z_TYPE(operandMax) != IS_NULL && UNEXPECTED(!scalarOp(add_function, &rangeMax, &operandMax, &max))) return zv::Val(); - } - break; - case IETR_MINUS: - /* [a, b] - [c, d] is [a - d, b - c] */ - if (Z_TYPE(rangeMin) != IS_NULL && Z_TYPE(operandMax) != IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); - } - if (Z_TYPE(rangeMax) != IS_NULL && Z_TYPE(operandMin) != IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMax, &operandMin, &max))) return zv::Val(); - } - break; - case IETR_MUL: { - zval minusInf, inf; - ZVAL_DOUBLE(&minusInf, -std::numeric_limits::infinity()); - ZVAL_DOUBLE(&inf, std::numeric_limits::infinity()); - zval min1, min2, max1, max2; - if (UNEXPECTED(!mulOrZero(&rangeMin, &operandMin, &minusInf, &minusInf, &min1))) return zv::Val(); - if (UNEXPECTED(!mulOrZero(&rangeMin, &operandMax, &minusInf, &inf, &min2))) return zv::Val(); - if (UNEXPECTED(!mulOrZero(&rangeMax, &operandMin, &inf, &minusInf, &max1))) return zv::Val(); - if (UNEXPECTED(!mulOrZero(&rangeMax, &operandMax, &inf, &inf, &max2))) return zv::Val(); - - zv::Args extremes{&min1, &min2, &max1, &max2}; - IETR_VAL(minValue, phpMinMax(false, 4, extremes)); - IETR_VAL(maxValue, phpMinMax(true, 4, extremes)); - ZVAL_COPY_VALUE(&min, minValue.raw()); - ZVAL_COPY_VALUE(&max, maxValue.raw()); - - if (!std::isfinite(zval_get_double(&min))) { - ZVAL_NULL(&min); - } - if (!std::isfinite(zval_get_double(&max))) { - ZVAL_NULL(&max); - } - break; - } - case IETR_DIV: { - if (operandIsConstant) { - if (Z_TYPE(rangeMin) != IS_NULL && Z_LVAL_P(operandValue) != 0) { - if (UNEXPECTED(!scalarOp(div_function, &rangeMin, operandValue, &min))) return zv::Val(); - } - if (Z_TYPE(rangeMax) != IS_NULL && Z_LVAL_P(operandValue) != 0) { - if (UNEXPECTED(!scalarOp(div_function, &rangeMax, operandValue, &max))) return zv::Val(); - } - } else { - // Avoid division by zero when looking for the min and the max by using the closest int - if (identical(&operandMin, &zero)) { - ZVAL_LONG(&operandMin, 1); - } - if (identical(&operandMax, &zero)) { - ZVAL_LONG(&operandMax, -1); - } - - if ( - (lessThan(&operandMin, &zero) || Z_TYPE(operandMin) == IS_NULL) - && (greaterThan(&operandMax, &zero) || Z_TYPE(operandMax) == IS_NULL) - ) { - IETR_VAL(negativeOperand, fromIntervalZv(&operandMin, &zero)); - IETR_VAL(positiveOperand, fromIntervalZv(&zero, &operandMax)); - return splitDivision(range, kind, negativeOperand.raw(), positiveOperand.raw(), true); - } - if ( - (lessThan(&rangeMin, &zero) || Z_TYPE(rangeMin) == IS_NULL) - && (greaterThan(&rangeMax, &zero) || Z_TYPE(rangeMax) == IS_NULL) - ) { - IETR_VAL(negativeRange, fromIntervalZv(&rangeMin, &zero)); - IETR_VAL(positiveRange, fromIntervalZv(&zero, &rangeMax)); - return splitDivision(operand.raw(), kind, negativeRange.raw(), positiveRange.raw(), false); - } - - zval minusInf, inf; - ZVAL_DOUBLE(&minusInf, -std::numeric_limits::infinity()); - ZVAL_DOUBLE(&inf, std::numeric_limits::infinity()); - zval *rangeMinOrInf = Z_TYPE(rangeMin) != IS_NULL ? &rangeMin : &minusInf; - zval *rangeMaxOrInf = Z_TYPE(rangeMax) != IS_NULL ? &rangeMax : &inf; - zval rangeMinSign, rangeMaxSign; - ZVAL_LONG(&rangeMinSign, zend_compare(rangeMinOrInf, &zero)); - ZVAL_LONG(&rangeMaxSign, zend_compare(rangeMaxOrInf, &zero)); - - zval negativeTenth, positiveTenth; - ZVAL_DOUBLE(&negativeTenth, -0.1); - ZVAL_DOUBLE(&positiveTenth, 0.1); - zval min1, min2, max1, max2; - if (UNEXPECTED(!scalarOp(Z_TYPE(operandMin) != IS_NULL ? div_function : mul_function, Z_TYPE(operandMin) != IS_NULL ? rangeMinOrInf : &rangeMinSign, Z_TYPE(operandMin) != IS_NULL ? &operandMin : &negativeTenth, &min1))) return zv::Val(); - if (UNEXPECTED(!scalarOp(Z_TYPE(operandMax) != IS_NULL ? div_function : mul_function, Z_TYPE(operandMax) != IS_NULL ? rangeMinOrInf : &rangeMinSign, Z_TYPE(operandMax) != IS_NULL ? &operandMax : &positiveTenth, &min2))) return zv::Val(); - if (UNEXPECTED(!scalarOp(Z_TYPE(operandMin) != IS_NULL ? div_function : mul_function, Z_TYPE(operandMin) != IS_NULL ? rangeMaxOrInf : &rangeMaxSign, Z_TYPE(operandMin) != IS_NULL ? &operandMin : &negativeTenth, &max1))) return zv::Val(); - if (UNEXPECTED(!scalarOp(Z_TYPE(operandMax) != IS_NULL ? div_function : mul_function, Z_TYPE(operandMax) != IS_NULL ? rangeMaxOrInf : &rangeMaxSign, Z_TYPE(operandMax) != IS_NULL ? &operandMax : &positiveTenth, &max2))) return zv::Val(); - - zv::Args extremes{&min1, &min2, &max1, &max2}; - IETR_VAL(minValue, phpMinMax(false, 4, extremes)); - IETR_VAL(maxValue, phpMinMax(true, 4, extremes)); - ZVAL_COPY_VALUE(&min, minValue.raw()); - ZVAL_COPY_VALUE(&max, maxValue.raw()); - - if (Z_TYPE(min) == IS_DOUBLE && Z_DVAL(min) == -std::numeric_limits::infinity()) { - ZVAL_NULL(&min); - } - if (Z_TYPE(max) == IS_DOUBLE && Z_DVAL(max) == std::numeric_limits::infinity()) { - ZVAL_NULL(&max); - } - } - - if (Z_TYPE(min) != IS_NULL && Z_TYPE(max) != IS_NULL && greaterThan(&min, &max)) { - zval swap; - ZVAL_COPY_VALUE(&swap, &min); - ZVAL_COPY_VALUE(&min, &max); - ZVAL_COPY_VALUE(&max, &swap); - } - - if (Z_TYPE(min) == IS_DOUBLE) { - toIntBound(std::ceil(Z_DVAL(min)), &min); - } - if (Z_TYPE(max) == IS_DOUBLE) { - toIntBound(std::floor(Z_DVAL(max)), &max); - } - - // invert maximas on division with negative constants - bool rangeNegativeConstant = false; - if (isA(range, pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(range), value))) return zv::Val(); - rangeNegativeConstant = value < 0; - } - if ((rangeNegativeConstant || (operandIsConstant && Z_LVAL_P(operandValue) < 0)) - && (Z_TYPE(min) == IS_NULL || Z_TYPE(max) == IS_NULL)) { - zval swap; - ZVAL_COPY_VALUE(&swap, &min); - ZVAL_COPY_VALUE(&min, &max); - ZVAL_COPY_VALUE(&max, &swap); - } - - if (Z_TYPE(min) == IS_NULL && Z_TYPE(max) == IS_NULL) { - return newIntFloatUnion(true); - } - - IETR_VAL(rangeType, fromIntervalZv(&min, &max)); - IETR_VAL(floatType, newFloatType()); - return union2(rangeType.raw(), floatType.raw()); - } - case IETR_SHIFT_LEFT: - case IETR_SHIFT_RIGHT: { - if (!operandIsConstant) { - return newIntegerType(); - } - if (Z_LVAL_P(operandValue) < 0) { - return newErrorType(); - } - if (kind == IETR_SHIFT_LEFT) { - // an overflowing shift wraps around, which breaks the monotonicity the bounds rely on - // open bounds still include the platform's minimum or maximum integer - bool minOverflows = false; - bool maxOverflows = false; - if (UNEXPECTED(!shiftLeftOverflows(Z_TYPE(rangeMin) == IS_NULL ? ZEND_LONG_MIN : zval_get_long(&rangeMin), operandValue, minOverflows))) return zv::Val(); - if (!minOverflows && UNEXPECTED(!shiftLeftOverflows(Z_TYPE(rangeMax) == IS_NULL ? ZEND_LONG_MAX : zval_get_long(&rangeMax), operandValue, maxOverflows))) return zv::Val(); - if (minOverflows || maxOverflows) { - return newIntegerType(); - } - } - pt_ietr_binary_op shift = kind == IETR_SHIFT_LEFT ? shift_left_function : shift_right_function; - if (Z_TYPE(rangeMin) != IS_NULL) { - zval rangeMinInteger; - ZVAL_LONG(&rangeMinInteger, zval_get_long(&rangeMin)); - if (UNEXPECTED(!scalarOp(shift, &rangeMinInteger, operandValue, &min))) return zv::Val(); - } - if (Z_TYPE(rangeMax) != IS_NULL) { - zval rangeMaxInteger; - ZVAL_LONG(&rangeMaxInteger, zval_get_long(&rangeMax)); - if (UNEXPECTED(!scalarOp(shift, &rangeMaxInteger, operandValue, &max))) return zv::Val(); - } - break; - } - default: - pt_throw_should_not_happen(); - return zv::Val(); - } - - if (Z_TYPE(min) == IS_DOUBLE) { - ZVAL_NULL(&min); - } - if (Z_TYPE(max) == IS_DOUBLE) { - ZVAL_NULL(&max); - } - - return fromIntervalZv(&min, &max); - } - - /* the Div arm's split around zero: TypeCombinator::union( - * integerRangeMath(fixed, negative), integerRangeMath(fixed, positive) - * )->toNumber() and the int|float -> benevolent rewrite; the split side - * is the operand (operandSplit) or the range */ - static zv::Val splitDivision(zval *fixed, IetrBinaryKind kind, zval *negative, zval *positive, bool operandSplit) - { - IETR_VAL(negativeResult, operandSplit ? integerRangeMath(fixed, kind, negative) : integerRangeMath(negative, kind, fixed)); - IETR_VAL(positiveResult, operandSplit ? integerRangeMath(fixed, kind, positive) : integerRangeMath(positive, kind, fixed)); - IETR_VAL(union_, union2(negativeResult.raw(), positiveResult.raw())); - IETR_VAL(result, callOn(union_.raw(), mToNumber)); - - IETR_VAL(intFloat, newIntFloatUnion(false)); - IETR_VAL(equals, callOn(result.raw(), mEquals, 1, intFloat.raw())); - if (zend_is_true(equals.raw())) { - return newIntFloatUnion(true); - } - - return result; - } - /* }}} */ /* {{{ negate() */ @@ -1887,399 +1540,6 @@ class OperatorTypeHelper /* }}} */ - /* {{{ floatRangeMath() */ - - /* the floats a member stands for: [min, max, whether NAN, whether an - * infinite bound is a value] (an unbounded int reaches the infinity on - * that side, which it never is); NAN alone is the empty interval - * [INF, -INF] */ - struct FloatInterval - { - double min; - double max; - bool nan; - bool infinities; - }; - - /* a member with the integers it is (none for a float member) and the - * floats it stands for */ - struct FloatIntervalMember - { - bool isInteger; - NumberSetData::IntegerInterval integers; - FloatInterval interval; - }; - - /* The denormal next to zero, the divisor of smallest magnitude. */ - static constexpr double SMALLEST_POSITIVE_FLOAT = 4.9E-324; - - /* Mirrors floatRangeMath(): null when no operand holds a float range or - * a member is not a number the intervals describe. */ - static zv::Val floatRangeMath(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) - { - bool leftHas = false, rightHas = false; - std::vector leftMembers, rightMembers; - if (UNEXPECTED(!getFloatIntervalMembers(leftType, leftHas, leftMembers))) return zv::Val(); - if (UNEXPECTED(!getFloatIntervalMembers(rightType, rightHas, rightMembers))) return zv::Val(); - if (!leftHas || !rightHas) return zv::Val::null(); - - bool hasFloatRange = false; - for (std::vector *members : {&leftMembers, &rightMembers}) { - for (FloatIntervalMember &member : *members) { - // a float member that is neither a constant nor plain float - if (member.isInteger || member.interval.min >= member.interval.max || member.interval.nan) continue; - hasFloatRange = true; - } - } - if (!hasFloatRange) return zv::Val::null(); - - if (kind == IETR_DIV) { - for (FloatIntervalMember &member : rightMembers) { - if (member.interval.min == 0.0 && member.interval.max == 0.0) return zv::Val::null(); - } - } - - if ((zend_long) leftMembers.size() * (zend_long) rightMembers.size() > PT_IETR_CALCULATE_SCALARS_LIMIT) { - IETR_TRI(leftIsFloat, triOn(leftType, mIsFloat)); - if (leftIsFloat == PT_TRI_YES) return newFloatType(); - IETR_TRI(rightIsFloat, triOn(rightType, mIsFloat)); - if (rightIsFloat == PT_TRI_YES) return newFloatType(); - - return newIntFloatUnion(false); - } - - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftMembers.size() * rightMembers.size())); - for (FloatIntervalMember &leftMember : leftMembers) { - for (FloatIntervalMember &rightMember : rightMembers) { - if (leftMember.isInteger && rightMember.isInteger) { - IETR_VAL(leftType_, pt_integer_range_from_interval(leftMember.integers.min, leftMember.integers.max, 0)); - IETR_VAL(rightType_, pt_integer_range_from_interval(rightMember.integers.min, rightMember.integers.max, 0)); - zv::Val pairResult; - if (kind == IETR_PLUS) { - pairResult = add(leftType_.raw(), rightType_.raw(), phpVersion); - } else if (kind == IETR_DIV) { - pairResult = divide(leftType_.raw(), rightType_.raw(), phpVersion); - } else { - pairResult = subtractOrMultiply(kind, leftType_.raw(), rightType_.raw(), phpVersion); - } - if (UNEXPECTED(pairResult.isUndef())) return zv::Val(); - resultTypes.push(std::move(pairResult)); - continue; - } - - bool splitDivisor = kind == IETR_DIV && rightMember.isInteger; - FloatInterval divisorIntervals[3]; - size_t divisorCount = splitDivisor ? splitIntegerDivisor(rightMember.interval, divisorIntervals) : 1; - if (!splitDivisor) divisorIntervals[0] = rightMember.interval; - for (size_t i = 0; i < divisorCount; i++) { - IETR_VAL(pairResult, floatIntervalMath(kind, leftMember.interval, divisorIntervals[i], phpVersion)); - resultTypes.push(std::move(pairResult)); - } - } - } - - return unionOf(resultTypes); - } - - /* Mirrors getFloatIntervalMembers(); has = false for its null; false = - * pending exception */ - [[nodiscard]] static bool getFloatIntervalMembers(zval *type, bool &has, std::vector &members) - { - has = false; - if (isA(type, pt_ce_benevolent_union_type)) return true; - - NumberSetData numberSet; - if (UNEXPECTED(!pt_type_to_number_set(type, numberSet, has))) return false; - if (!has) return true; - - for (const NumberSetData::IntegerInterval &integers : numberSet.integerIntervals) { - members.push_back({true, integers, { - integers.min.isNull ? -HUGE_VAL : static_cast(integers.min.value), - integers.max.isNull ? HUGE_VAL : static_cast(integers.max.value), - false, - false, - }}); - } - // plain float is one member: the full interval and NAN - bool plainFloat = isPlainFloat(numberSet); - NumberSetData::IntegerInterval noIntegers = {NullableLong::null(), NullableLong::null()}; - for (const NumberSetData::FloatInterval &floats : numberSet.floatIntervals) { - members.push_back({false, noIntegers, {floats.min, floats.max, plainFloat, true}}); - } - if (numberSet.nan && !plainFloat) { - members.push_back({false, noIntegers, {HUGE_VAL, -HUGE_VAL, true, true}}); - } - - return true; - } - - /* Mirrors isPlainFloat(). */ - static bool isPlainFloat(const NumberSetData &numberSet) - { - return numberSet.nan - && numberSet.floatIntervals.size() == 1 - && numberSet.floatIntervals[0].min == -HUGE_VAL - && numberSet.floatIntervals[0].max == HUGE_VAL; - } - - /* getFloatIntervalMembers() for the private method's glue */ - static zv::Val getFloatIntervalMembersValue(zval *type) - { - bool has = false; - std::vector members; - if (UNEXPECTED(!getFloatIntervalMembers(type, has, members))) return zv::Val(); - if (!has) return zv::Val::null(); - - zv::Arr list = zv::Arr::create((uint32_t) members.size()); - for (FloatIntervalMember &member : members) { - zv::Arr pair = zv::Arr::create(2); - pair.push(member.isInteger ? integerIntervalValue(member.integers) : zv::Val::null()); - pair.push(floatIntervalValue(member.interval)); - list.push(zv::Val(std::move(pair))); - } - - return zv::Val(std::move(list)); - } - - /* Mirrors floatIntervalMath(). */ - static zv::Val floatIntervalMath(IetrBinaryKind kind, FloatInterval left, FloatInterval right, zval *phpVersion) - { - double leftMin = left.min, leftMax = left.max; - double rightMin = right.min, rightMax = right.max; - bool nan = left.nan || right.nan; - zv::Arr resultTypes = zv::Arr::create(4); - - if (leftMin <= leftMax && rightMin <= rightMax) { - bool leftHasZero = leftMin <= 0.0 && leftMax >= 0.0; - bool rightHasZero = rightMin <= 0.0 && rightMax >= 0.0; - // the infinities that stand for an unbounded int are not values - bool leftHasNegativeInfinity = left.infinities && leftMin == -HUGE_VAL; - bool leftHasPositiveInfinity = left.infinities && leftMax == HUGE_VAL; - bool rightHasNegativeInfinity = right.infinities && rightMin == -HUGE_VAL; - bool rightHasPositiveInfinity = right.infinities && rightMax == HUGE_VAL; - bool leftHasInfinity = leftHasNegativeInfinity || leftHasPositiveInfinity; - bool rightHasInfinity = rightHasNegativeInfinity || rightHasPositiveInfinity; - - double rightIntervals[2][2] = {{rightMin, rightMax}, {0.0, 0.0}}; - size_t rightIntervalCount = 1; - if (kind == IETR_PLUS) { - nan = nan || (leftHasPositiveInfinity && rightHasNegativeInfinity) || (leftHasNegativeInfinity && rightHasPositiveInfinity); - } else if (kind == IETR_MINUS) { - nan = nan || (leftHasPositiveInfinity && rightHasPositiveInfinity) || (leftHasNegativeInfinity && rightHasNegativeInfinity); - } else if (kind == IETR_MUL) { - nan = nan || (leftHasZero && rightHasInfinity) || (rightHasZero && leftHasInfinity); - } else { - nan = nan || (leftHasInfinity && rightHasInfinity); - if (rightHasZero) { - // both zeros are divisors: x / 0.0 and x / -0.0 are opposite infinities - rightIntervalCount = 0; - if (rightMin < 0.0) { - rightIntervals[rightIntervalCount][0] = rightMin; - rightIntervals[rightIntervalCount][1] = -SMALLEST_POSITIVE_FLOAT; - rightIntervalCount++; - } - if (rightMax > 0.0) { - rightIntervals[rightIntervalCount][0] = SMALLEST_POSITIVE_FLOAT; - rightIntervals[rightIntervalCount][1] = rightMax; - rightIntervalCount++; - } - bool throws; - if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_THROWS_ON_DIVISION_BY_ZERO, throws))) return zv::Val(); - if (!throws) { - nan = nan || leftHasZero; - if (leftMin < 0.0 || leftMax > 0.0) { - IETR_VAL(full, pt_float_range_from_interval(-HUGE_VAL, HUGE_VAL)); - resultTypes.push(std::move(full)); - } - } - } - } - - for (size_t i = 0; i < rightIntervalCount; i++) { - double intervalMin = rightIntervals[i][0]; - double intervalMax = rightIntervals[i][1]; - bool hasCorner = false; - double cornerMin = 0.0, cornerMax = 0.0; - auto addCorner = [&](double corner) { - if (!hasCorner) { - cornerMin = corner; - cornerMax = corner; - hasCorner = true; - return; - } - // min() / max() keep the first of equal values - if (corner < cornerMin) cornerMin = corner; - if (corner > cornerMax) cornerMax = corner; - }; - for (double leftBound : {leftMin, leftMax}) { - for (double rightBound : {intervalMin, intervalMax}) { - double corner = calculateFloat(kind, leftBound, rightBound); - if (!std::isnan(corner)) { - addCorner(corner); - continue; - } - - // the NAN of a corner is added above, the floats next to it bound the rest - double leftValues[2], rightValues[2]; - size_t leftCount = getFloatBoundWithInnerNeighbour(leftBound, leftMin, leftMax, leftValues); - size_t rightCount = getFloatBoundWithInnerNeighbour(rightBound, intervalMin, intervalMax, rightValues); - for (size_t l = 0; l < leftCount; l++) { - for (size_t r = 0; r < rightCount; r++) { - corner = calculateFloat(kind, leftValues[l], rightValues[r]); - if (std::isnan(corner)) continue; - addCorner(corner); - } - } - } - } - - if (!hasCorner) continue; - - IETR_VAL(range, pt_float_range_from_interval(cornerMin, cornerMax)); - resultTypes.push(std::move(range)); - } - } - - if (nan) { - IETR_VAL(nanType, newConstantFloatType(static_cast(NAN))); - resultTypes.push(std::move(nanType)); - } - - return unionOf(resultTypes); - } - - /* Mirrors calculateFloat(). */ - static double calculateFloat(IetrBinaryKind kind, double left, double right) - { - if (kind == IETR_PLUS) return left + right; - if (kind == IETR_MINUS) return left - right; - if (kind == IETR_MUL) return left * right; - - return left / right; - } - - /* Mirrors splitIntegerDivisor(): an integer divisor next to zero is -1 - * or 1, not a denormal; the count of intervals written */ - static size_t splitIntegerDivisor(FloatInterval interval, FloatInterval out[3]) - { - if (interval.min > 0.0 || interval.max < 0.0) { - out[0] = interval; - return 1; - } - - size_t count = 0; - if (interval.min < 0.0) out[count++] = {interval.min, -1.0, false, false}; - out[count++] = {0.0, 0.0, false, false}; - if (interval.max > 0.0) out[count++] = {1.0, interval.max, false, false}; - - return count; - } - - /* splitIntegerDivisor() for the private method's glue */ - static zv::Val splitIntegerDivisorValue(FloatInterval interval) - { - FloatInterval intervals[3]; - size_t count = splitIntegerDivisor(interval, intervals); - zv::Arr list = zv::Arr::create((uint32_t) count); - for (size_t i = 0; i < count; i++) { - list.push(floatIntervalValue(intervals[i])); - } - - return zv::Val(std::move(list)); - } - - /* the array{float, float, bool, bool} of an interval */ - static zv::Val floatIntervalValue(const FloatInterval &interval) - { - zval min, max; - ZVAL_DOUBLE(&min, interval.min); - ZVAL_DOUBLE(&max, interval.max); - zv::Arr value = zv::Arr::create(4); - value.push(zv::Val::adopt(min)); - value.push(zv::Val::adopt(max)); - value.push(zv::Val::boolean(interval.nan)); - value.push(zv::Val::boolean(interval.infinities)); - return zv::Val(std::move(value)); - } - - /* Mirrors getIntegerDivisionFloatType(): null when an operand is not - * made of numbers or every quotient but NAN is possible */ - static zv::Val getIntegerDivisionFloatType(zval *leftType, zval *rightType, zval *phpVersion) - { - bool leftHas = false, rightHas = false; - std::vector leftMembers, rightMembers; - if (UNEXPECTED(!getFloatIntervalMembers(leftType, leftHas, leftMembers))) return zv::Val(); - if (UNEXPECTED(!getFloatIntervalMembers(rightType, rightHas, rightMembers))) return zv::Val(); - if (!leftHas || !rightHas) return zv::Val::null(); - if ((zend_long) leftMembers.size() * (zend_long) rightMembers.size() > PT_IETR_CALCULATE_SCALARS_LIMIT) return zv::Val::null(); - - zv::Arr floatTypes = zv::Arr::create((uint32_t) (leftMembers.size() * rightMembers.size() * 3)); - for (FloatIntervalMember &leftMember : leftMembers) { - for (FloatIntervalMember &rightMember : rightMembers) { - FloatInterval divisorIntervals[3]; - size_t divisorCount = splitIntegerDivisor(rightMember.interval, divisorIntervals); - for (size_t i = 0; i < divisorCount; i++) { - IETR_VAL(floatType, floatIntervalMath(IETR_DIV, leftMember.interval, divisorIntervals[i], phpVersion)); - floatTypes.push(std::move(floatType)); - } - } - } - - // a quotient that can be any float but NAN reads better as float, like int + 1.5 does - IETR_VAL(floatType, unionOf(floatTypes)); - IETR_VAL(fullRange, pt_float_range_from_interval(-HUGE_VAL, HUGE_VAL)); - IETR_VAL(isFullRange, callOn(floatType.raw(), mEquals, 1, fullRange.raw())); - if (Z_TYPE_P(isFullRange.raw()) == IS_TRUE) return zv::Val::null(); - - return floatType; - } - - /* Mirrors replaceFloatType(): a plain FloatType, alone or in a union, - * replaced by floatType */ - static zv::Val replaceFloatType(zval *type, zval *floatType) - { - if (Z_TYPE_P(type) == IS_OBJECT && Z_OBJCE_P(type) == pt_ce_float_type) return zv::Val::copyOf(zv::Ref(floatType)); - if (!isA(type, pt_ce_union_type)) return zv::Val::copyOf(zv::Ref(type)); - - IETR_VAL(innerTypes, callOn(type, mGetTypes)); - if (UNEXPECTED(!requireArray(innerTypes.raw()))) return zv::Val(); - zv::Arr types = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(innerTypes.raw()))); - for (zv::ArrayEntry entry : zv::ArrRef(innerTypes.raw())) { - zval *innerType = entry.value().raw(); - bool isFloat = Z_TYPE_P(innerType) == IS_OBJECT && Z_OBJCE_P(innerType) == pt_ce_float_type; - types.push(zv::Ref(isFloat ? floatType : innerType)); - } - - IETR_VAL(union_, unionOf(types)); - if (isA(type, pt_ce_benevolent_union_type)) return toBenevolentUnion(union_.raw()); - - return union_; - } - - /* Mirrors getFloatBoundWithInnerNeighbour(): the count of values written */ - static size_t getFloatBoundWithInnerNeighbour(double bound, double min, double max, double out[2]) - { - out[0] = bound; - if (min == max) return 1; - - if (bound == HUGE_VAL) { - out[1] = std::numeric_limits::max(); - return 2; - } - if (bound == -HUGE_VAL) { - out[1] = -std::numeric_limits::max(); - return 2; - } - if (bound == 0.0) { - out[1] = bound == min ? SMALLEST_POSITIVE_FLOAT : -SMALLEST_POSITIVE_FLOAT; - return 2; - } - - return 1; - } - - /* }}} */ - /* {{{ small private helpers of the twin */ /* Mirrors getNeverType(). */ @@ -2307,20 +1567,6 @@ class OperatorTypeHelper /* }}} */ - /* private static: a left shift that does not fit into an integer wraps - * around instead of turning into a float, so the shifted value no longer - * preserves the ordering of its operand — `($value << $shift) >> $shift - * !== $value` through the engine's operators; false = pending exception */ - [[nodiscard]] static bool shiftLeftOverflows(zend_long value, zval *shift, bool &out) - { - zval operand, shifted, restored; - ZVAL_LONG(&operand, value); - if (UNEXPECTED(!scalarOp(shift_left_function, &operand, shift, &shifted))) return false; - if (UNEXPECTED(!scalarOp(shift_right_function, &shifted, shift, &restored))) return false; - out = Z_TYPE(restored) != IS_LONG || Z_LVAL(restored) != value; - return true; - } - /* {{{ intDivide() */ /* an interval bound of intDivide(): an int, or the twin's -INF / INF @@ -2545,9 +1791,9 @@ zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval case PT_OPERATOR_BITWISE_XOR: return OperatorTypeHelper::bitwise(IETR_BITWISE_XOR, left, right); case PT_OPERATOR_SHIFT_LEFT: - return OperatorTypeHelper::shift(IETR_SHIFT_LEFT, left, right); + return OperatorTypeHelper::shift(IETR_SHIFT_LEFT, left, right, phpVersion); case PT_OPERATOR_SHIFT_RIGHT: - return OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, left, right); + return OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, left, right, phpVersion); case PT_OPERATOR_CONCATENATE: return OperatorTypeHelper::concat(left, right, phpVersion); case PT_OPERATOR_ARRAY_UNION: @@ -2588,30 +1834,7 @@ bool otArgPhpVersion(zval *value, uint32_t arg) return false; } -/* the array{float, float, bool, bool} of the interval parameters - * (PHPStan checks the shape, the engine does not); false = thrown */ -bool otFloatInterval(zval *value, uint32_t arg, OperatorTypeHelper::FloatInterval &out) -{ - if (Z_TYPE_P(value) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(value)) == 4) { - zval *min = zend_hash_index_find(Z_ARRVAL_P(value), 0); - zval *max = zend_hash_index_find(Z_ARRVAL_P(value), 1); - zval *nan = zend_hash_index_find(Z_ARRVAL_P(value), 2); - zval *infinities = zend_hash_index_find(Z_ARRVAL_P(value), 3); - if ( - min != NULL && max != NULL && nan != NULL && infinities != NULL - && Z_TYPE_P(min) == IS_DOUBLE && Z_TYPE_P(max) == IS_DOUBLE - && (Z_TYPE_P(nan) == IS_TRUE || Z_TYPE_P(nan) == IS_FALSE) - && (Z_TYPE_P(infinities) == IS_TRUE || Z_TYPE_P(infinities) == IS_FALSE) - ) { - out = {Z_DVAL_P(min), Z_DVAL_P(max), Z_TYPE_P(nan) == IS_TRUE, Z_TYPE_P(infinities) == IS_TRUE}; - return true; - } - } - zend_argument_type_error(arg, "must be of type array{float, float, bool, bool}, %s given", zend_zval_value_name(value)); - return false; -} - -/* the operator string of resolveCommonMath() / integerRangeMath(); false = +/* the operator string of resolveCommonMath(); false = * the ValueError of an operator the twin does not handle */ bool otOperatorKind(zend_string *operator_, IetrBinaryKind &kind) { @@ -2672,7 +1895,6 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) ptdecl::OperatorTypeHelper::declareClass(cls); cls.privateClassConstantLong("IS_SCALAR_TYPE", PT_IETR_IS_SCALAR_TYPE); cls.privateClassConstantLong("IS_UNKNOWN", PT_IETR_IS_UNKNOWN); - cls.privateClassConstantValue("SMALLEST_POSITIVE_FLOAT", [](zval *out) { ZVAL_DOUBLE(out, OperatorTypeHelper::SMALLEST_POSITIVE_FLOAT); }); ptdecl::OperatorTypeHelper::declareProperties(cls); cls.method(sigs::concat, OT_BINARY_GLUE(OperatorTypeHelper::concat(leftType, rightType, phpVersion))); @@ -2696,7 +1918,7 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) }); cls.method(sigs::divide, OT_BINARY_GLUE(OperatorTypeHelper::divide(leftType, rightType, phpVersion))); - cls.method(sigs::getDivTypeFromTypes, OT_TYPES_GLUE(OperatorTypeHelper::getDivTypeFromTypes(leftType, rightType))); + cls.method(sigs::getDivTypeFromTypes, OT_BINARY_GLUE(OperatorTypeHelper::getDivTypeFromTypes(leftType, rightType, phpVersion))); cls.method(sigs::modulo, OT_BINARY_GLUE(OperatorTypeHelper::modulo(leftType, rightType))); cls.method(sigs::getMaxModuloMagnitude, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -2719,8 +1941,8 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) cls.method(sigs::tryArrayUnion, OT_TYPES_GLUE(OperatorTypeHelper::tryArrayUnion(leftType, rightType))); cls.method(sigs::subtract, OT_BINARY_GLUE(OperatorTypeHelper::subtractOrMultiply(IETR_MINUS, leftType, rightType, phpVersion))); cls.method(sigs::multiply, OT_BINARY_GLUE(OperatorTypeHelper::subtractOrMultiply(IETR_MUL, leftType, rightType, phpVersion))); - cls.method(sigs::shiftLeft, OT_BINARY_GLUE(OperatorTypeHelper::shift(IETR_SHIFT_LEFT, leftType, rightType))); - cls.method(sigs::shiftRight, OT_BINARY_GLUE(OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, leftType, rightType))); + cls.method(sigs::shiftLeft, OT_BINARY_GLUE(OperatorTypeHelper::shift(IETR_SHIFT_LEFT, leftType, rightType, phpVersion))); + cls.method(sigs::shiftRight, OT_BINARY_GLUE(OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, leftType, rightType, phpVersion))); cls.method(sigs::optimizeScalarType, OT_TYPE_GLUE(OperatorTypeHelper::optimizeScalarType(type))); cls.method(sigs::canEndInWhitespace, [](INTERNAL_FUNCTION_PARAMETERS) { zval *left, *leftStringType, *phpVersion; @@ -2742,40 +1964,16 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) cls.method(sigs::resolveCommonMath, [](INTERNAL_FUNCTION_PARAMETERS) { zend_string *operator_; - zval *leftType, *rightType; - if (!zp::parse(execute_data, operator_, leftType, rightType)) RETURN_THROWS(); - if (UNEXPECTED(!otArgType(leftType, 2) || !otArgType(rightType, 3))) RETURN_THROWS(); - IetrBinaryKind kind; - if (UNEXPECTED(!otOperatorKind(operator_, kind))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::resolveCommonMath(kind, leftType, rightType)); - }); - - cls.method(sigs::integerRangeMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *range, *operand; - zend_string *operator_; - if (!zp::parse(execute_data, range, operator_, operand)) RETURN_THROWS(); - if (UNEXPECTED(!otArgType(range, 1) || !otArgType(operand, 3))) RETURN_THROWS(); + zval *leftType, *rightType, *phpVersion; + if (!zp::parse(execute_data, operator_, leftType, rightType, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!otArgType(leftType, 2) || !otArgType(rightType, 3) || !otArgPhpVersion(phpVersion, 4))) RETURN_THROWS(); IetrBinaryKind kind; if (UNEXPECTED(!otOperatorKind(operator_, kind))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::integerRangeMath(range, kind, operand)); - }); - - cls.method(sigs::shiftLeftOverflows, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_long value, shift; - if (!zp::parse(execute_data, value, shift)) RETURN_THROWS(); - bool out = false; - if (UNEXPECTED(!OperatorTypeHelper::shiftLeftOverflowsValue(value, shift, out))) RETURN_THROWS(); - RETURN_BOOL(out); + PT_RETURN_VAL(OperatorTypeHelper::resolveCommonMath(kind, leftType, rightType, phpVersion)); }); cls.method(sigs::negateNumber, OT_TYPE_GLUE(OperatorTypeHelper::negateNumber(type))); - cls.method(sigs::toIntBound, [](INTERNAL_FUNCTION_PARAMETERS) { - double value; - if (!zp::parse(execute_data, value)) RETURN_THROWS(); - OperatorTypeHelper::toIntBound(value, return_value); - }); - cls.method(sigs::getTypeFromValue, [](INTERNAL_FUNCTION_PARAMETERS) { zval *value; if (!zp::parse(execute_data, value)) RETURN_THROWS(); @@ -2807,75 +2005,6 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) cls.method(sigs::intDivide, OT_BINARY_GLUE(OperatorTypeHelper::intDivide(leftType, rightType))); - cls.method(sigs::floatRangeMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_string *operator_; - zval *leftType, *rightType, *phpVersion; - if (!zp::parse(execute_data, operator_, leftType, rightType, phpVersion)) RETURN_THROWS(); - if (UNEXPECTED(!otArgType(leftType, 2) || !otArgType(rightType, 3) || !otArgPhpVersion(phpVersion, 4))) RETURN_THROWS(); - IetrBinaryKind kind; - if (UNEXPECTED(!otOperatorKind(operator_, kind))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::floatRangeMath(kind, leftType, rightType, phpVersion)); - }); - - cls.method(sigs::getFloatIntervalMembers, OT_TYPE_GLUE(OperatorTypeHelper::getFloatIntervalMembersValue(type))); - cls.method(sigs::isPlainFloat, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *numberSet; - if (!zp::parse(execute_data, numberSet)) RETURN_THROWS(); - phpstanturbo::NumberSetData data; - if (UNEXPECTED(!pt_number_set_read(Z_OBJ_P(numberSet), data))) RETURN_THROWS(); - RETURN_BOOL(OperatorTypeHelper::isPlainFloat(data)); - }); - - cls.method(sigs::splitIntegerDivisor, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *interval; - if (!zp::parse(execute_data, interval)) RETURN_THROWS(); - OperatorTypeHelper::FloatInterval parsed; - if (UNEXPECTED(!otFloatInterval(interval, 1, parsed))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::splitIntegerDivisorValue(parsed)); - }); - - cls.method(sigs::getIntegerDivisionFloatType, OT_BINARY_GLUE(OperatorTypeHelper::getIntegerDivisionFloatType(leftType, rightType, phpVersion))); - cls.method(sigs::replaceFloatType, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *type, *floatType; - if (!zp::parse(execute_data, type, floatType)) RETURN_THROWS(); - if (UNEXPECTED(!otArgType(type, 1) || !otArgType(floatType, 2))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::replaceFloatType(type, floatType)); - }); - - cls.method(sigs::floatIntervalMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_string *operator_; - zval *left, *right, *phpVersion; - if (!zp::parse(execute_data, operator_, left, right, phpVersion)) RETURN_THROWS(); - if (UNEXPECTED(!otArgPhpVersion(phpVersion, 4))) RETURN_THROWS(); - IetrBinaryKind kind; - if (UNEXPECTED(!otOperatorKind(operator_, kind))) RETURN_THROWS(); - OperatorTypeHelper::FloatInterval intervals[2]; - zval *arguments[2] = { left, right }; - for (uint32_t i = 0; i < 2; i++) { - if (UNEXPECTED(!otFloatInterval(arguments[i], i + 2, intervals[i]))) RETURN_THROWS(); - } - PT_RETURN_VAL(OperatorTypeHelper::floatIntervalMath(kind, intervals[0], intervals[1], phpVersion)); - }); - - cls.method(sigs::calculateFloat, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_string *operator_; - double left, right; - if (!zp::parse(execute_data, operator_, left, right)) RETURN_THROWS(); - IetrBinaryKind kind; - if (UNEXPECTED(!otOperatorKind(operator_, kind))) RETURN_THROWS(); - RETURN_DOUBLE(OperatorTypeHelper::calculateFloat(kind, left, right)); - }); - - cls.method(sigs::getFloatBoundWithInnerNeighbour, [](INTERNAL_FUNCTION_PARAMETERS) { - double bound, min, max; - if (!zp::parse(execute_data, bound, min, max)) RETURN_THROWS(); - double values[2]; - size_t count = OperatorTypeHelper::getFloatBoundWithInnerNeighbour(bound, min, max, values); - array_init_size(return_value, (uint32_t) count); - for (size_t i = 0; i < count; i++) { - add_next_index_double(return_value, values[i]); - } - }); cls.method(sigs::getIntervals, OT_TYPE_GLUE(OperatorTypeHelper::getIntervalsValue(type))); cls.method(sigs::divideIntervals, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/generated/NumberSet.h b/turbo-ext/src/generated/NumberSet.h index 69ed1f28b54..56b4d755c5d 100644 --- a/turbo-ext/src/generated/NumberSet.h +++ b/turbo-ext/src/generated/NumberSet.h @@ -38,7 +38,44 @@ inline constexpr char strings[] = "\0" /* 48 */ "getIntegerIntervals\0" /* 49 */ "getFloatIntervals\0" /* 69 */ - "hasNan"; /* 87 */ + "hasNan\0" /* 87 */ + "right\0" /* 94 */ + "PHPStan\\Type\\NumberSet\0" /* 100 */ + "phpVersion\0" /* 123 */ + "PHPStan\\Php\\PhpVersion\0" /* 134 */ + "PHPStan\\Type\\Type\0" /* 157 */ + "add\0" /* 175 */ + "subtract\0" /* 179 */ + "multiply\0" /* 188 */ + "divide\0" /* 197 */ + "shiftLeft\0" /* 204 */ + "shiftRight\0" /* 214 */ + "operator\0" /* 225 */ + "calculate\0" /* 234 */ + "hasAnyInteger\0" /* 244 */ + "countMembers\0" /* 258 */ + "isEmpty\0" /* 271 */ + "hasFloatRange\0" /* 279 */ + "toHull\0" /* 293 */ + "interval\0" /* 300 */ + "integerToFloatInterval\0" /* 309 */ + "splitIntegerDivisor\0" /* 332 */ + "left\0" /* 352 */ + "calculateIntegers\0" /* 357 */ + "value\0" /* 375 */ + "shift\0" /* 381 */ + "shiftLeftOverflows\0" /* 387 */ + "divideIntegers\0" /* 406 */ + "numerator\0" /* 421 */ + "divisor\0" /* 431 */ + "divideBound\0" /* 439 */ + "toIntBound\0" /* 451 */ + "calculateFloats\0" /* 462 */ + "calculateFloat\0" /* 478 */ + "bound\0" /* 493 */ + "min\0" /* 499 */ + "max\0" /* 503 */ + "getFloatBoundWithInnerNeighbour"; /* 507 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $integerIntervals */ reg::packed(17, MAY_BE_ARRAY), /* __construct $floatIntervals */ @@ -46,6 +83,66 @@ inline constexpr reg::PackedArg args[] = { reg::packed(48, MAY_BE_ARRAY), /* getIntegerIntervals return */ reg::packed(48, MAY_BE_ARRAY), /* getFloatIntervals return */ reg::packed(48, MAY_BE_BOOL), /* hasNan return */ + reg::packed(94, 0, 100), /* add $right */ + reg::packed(123, 0, 134), /* add $phpVersion */ + reg::packed(48, 0, 157), /* add return */ + reg::packed(94, 0, 100), /* subtract $right */ + reg::packed(123, 0, 134), /* subtract $phpVersion */ + reg::packed(48, 0, 157), /* subtract return */ + reg::packed(94, 0, 100), /* multiply $right */ + reg::packed(123, 0, 134), /* multiply $phpVersion */ + reg::packed(48, 0, 157), /* multiply return */ + reg::packed(94, 0, 100), /* divide $right */ + reg::packed(123, 0, 134), /* divide $phpVersion */ + reg::packed(48, 0, 157), /* divide return */ + reg::packed(94, 0, 100), /* shiftLeft $right */ + reg::packed(123, 0, 134), /* shiftLeft $phpVersion */ + reg::packed(48, 0, 157), /* shiftLeft return */ + reg::packed(94, 0, 100), /* shiftRight $right */ + reg::packed(123, 0, 134), /* shiftRight $phpVersion */ + reg::packed(48, 0, 157), /* shiftRight return */ + reg::packed(225, MAY_BE_STRING), /* calculate $operator */ + reg::packed(94, 0, 100), /* calculate $right */ + reg::packed(123, 0, 134), /* calculate $phpVersion */ + reg::packed(48, 0, 157), /* calculate return */ + reg::packed(48, MAY_BE_BOOL), /* hasAnyInteger return */ + reg::packed(48, MAY_BE_LONG), /* countMembers return */ + reg::packed(48, MAY_BE_BOOL), /* isEmpty return */ + reg::packed(48, MAY_BE_BOOL), /* hasFloatRange return */ + reg::packed(48, 0, 100), /* toHull return */ + reg::packed(300, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ + reg::packed(48, MAY_BE_ARRAY), /* integerToFloatInterval return */ + reg::packed(300, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ + reg::packed(48, MAY_BE_ARRAY), /* splitIntegerDivisor return */ + reg::packed(225, MAY_BE_STRING), /* calculateIntegers $operator */ + reg::packed(352, MAY_BE_ARRAY), /* calculateIntegers $left */ + reg::packed(94, MAY_BE_ARRAY), /* calculateIntegers $right */ + reg::packed(48, 0, 157), /* calculateIntegers return */ + reg::packed(375, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(381, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(48, MAY_BE_BOOL), /* shiftLeftOverflows return */ + reg::packed(352, MAY_BE_ARRAY), /* divideIntegers $left */ + reg::packed(94, MAY_BE_ARRAY), /* divideIntegers $right */ + reg::packed(123, 0, 134), /* divideIntegers $phpVersion */ + reg::packed(48, MAY_BE_ARRAY), /* divideIntegers return */ + reg::packed(421, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ + reg::packed(431, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ + reg::packed(48, MAY_BE_ARRAY), /* divideBound return */ + reg::packed(375, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ + reg::packed(225, MAY_BE_STRING), /* calculateFloats $operator */ + reg::packed(352, MAY_BE_ARRAY), /* calculateFloats $left */ + reg::packed(94, MAY_BE_ARRAY), /* calculateFloats $right */ + reg::packed(123, 0, 134), /* calculateFloats $phpVersion */ + reg::packed(48, 0, 157), /* calculateFloats return */ + reg::packed(225, MAY_BE_STRING), /* calculateFloat $operator */ + reg::packed(352, MAY_BE_DOUBLE), /* calculateFloat $left */ + reg::packed(94, MAY_BE_DOUBLE), /* calculateFloat $right */ + reg::packed(48, MAY_BE_DOUBLE), /* calculateFloat return */ + reg::packed(493, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ + reg::packed(499, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ + reg::packed(503, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ + reg::packed(48, MAY_BE_ARRAY), /* getFloatBoundWithInnerNeighbour return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -56,6 +153,28 @@ inline constexpr sigtab::Sig __construct = { { 36 /* __construct */, 3, 0, 3, re inline constexpr sigtab::Sig getIntegerIntervals = { { 49 /* getIntegerIntervals */, 0, 3, 0, 3, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFloatIntervals = { { 69 /* getFloatIntervals */, 0, 4, 0, 4, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig hasNan = { { 87 /* hasNan */, 0, 5, 0, 5, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig add = { { 175 /* add */, 2, 6, 2, 8, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig subtract = { { 179 /* subtract */, 2, 9, 2, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig multiply = { { 188 /* multiply */, 2, 12, 2, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig divide = { { 197 /* divide */, 2, 15, 2, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 204 /* shiftLeft */, 2, 18, 2, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftRight = { { 214 /* shiftRight */, 2, 21, 2, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculate = { { 234 /* calculate */, 3, 24, 3, 27, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasAnyInteger = { { 244 /* hasAnyInteger */, 0, 28, 0, 28, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig countMembers = { { 258 /* countMembers */, 0, 29, 0, 29, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isEmpty = { { 271 /* isEmpty */, 0, 30, 0, 30, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasFloatRange = { { 279 /* hasFloatRange */, 0, 31, 0, 31, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toHull = { { 293 /* toHull */, 0, 32, 0, 32, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig integerToFloatInterval = { { 309 /* integerToFloatInterval */, 1, 33, 1, 34, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig splitIntegerDivisor = { { 332 /* splitIntegerDivisor */, 1, 35, 1, 36, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateIntegers = { { 357 /* calculateIntegers */, 3, 37, 3, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 387 /* shiftLeftOverflows */, 2, 41, 2, 43, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntegers = { { 406 /* divideIntegers */, 3, 44, 3, 47, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBound = { { 439 /* divideBound */, 2, 48, 2, 50, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 451 /* toIntBound */, 1, 51, 1, 52, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloats = { { 462 /* calculateFloats */, 4, 53, 4, 57, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloat = { { 478 /* calculateFloat */, 3, 58, 3, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 507 /* getFloatBoundWithInnerNeighbour */, 3, 62, 3, 65, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::NumberSet diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index 45965ca210b..aa6c878ed7f 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -39,69 +39,47 @@ inline constexpr char strings[] = "operationCallable\0" /* 156 */ "getFiniteOrConstantScalarTypes\0" /* 174 */ "divide\0" /* 205 */ - "getIntegerDivisionFloatType\0" /* 212 */ - "type\0" /* 240 */ - "floatType\0" /* 245 */ - "replaceFloatType\0" /* 255 */ - "getDivTypeFromTypes\0" /* 272 */ - "modulo\0" /* 292 */ - "divisorMin\0" /* 299 */ - "divisorMax\0" /* 310 */ - "getMaxModuloMagnitude\0" /* 321 */ - "getIntegerBounds\0" /* 343 */ - "getIntegerIntervals\0" /* 360 */ - "add\0" /* 380 */ - "arrayUnion\0" /* 384 */ - "tryArrayUnion\0" /* 395 */ - "subtract\0" /* 409 */ - "multiply\0" /* 418 */ - "shiftLeft\0" /* 427 */ - "shiftRight\0" /* 437 */ - "optimizeScalarType\0" /* 448 */ - "getNonNegativeIntegerBounds\0" /* 467 */ - "leftNumberType\0" /* 495 */ - "rightNumberType\0" /* 510 */ - "computeBitwiseAndRange\0" /* 526 */ - "computeBitwiseOrXorRange\0" /* 549 */ - "value\0" /* 574 */ - "allBitsMask\0" /* 580 */ - "operator\0" /* 592 */ - "resolveCommonMath\0" /* 601 */ - "range\0" /* 619 */ - "operand\0" /* 625 */ - "integerRangeMath\0" /* 633 */ - "shift\0" /* 650 */ - "shiftLeftOverflows\0" /* 656 */ - "negateNumber\0" /* 675 */ - "toIntBound\0" /* 688 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 699 */ - "toIntegerOperand\0" /* 731 */ - "getTypeFromValue\0" /* 748 */ - "floatRangeMath\0" /* 765 */ - "interval\0" /* 780 */ - "splitIntegerDivisor\0" /* 789 */ - "getFloatIntervalMembers\0" /* 809 */ - "numberSet\0" /* 833 */ - "PHPStan\\Type\\NumberSet\0" /* 843 */ - "isPlainFloat\0" /* 866 */ - "floatIntervalMath\0" /* 879 */ - "calculateFloat\0" /* 897 */ - "bound\0" /* 912 */ - "min\0" /* 918 */ - "max\0" /* 922 */ - "getFloatBoundWithInnerNeighbour\0" /* 926 */ - "getNeverType\0" /* 958 */ - "negate\0" /* 971 */ - "numeratorType\0" /* 978 */ - "divisorType\0" /* 992 */ - "intDivide\0" /* 1004 */ - "getIntervals\0" /* 1014 */ - "numeratorMin\0" /* 1027 */ - "numeratorMax\0" /* 1040 */ - "divideIntervals\0" /* 1053 */ - "numerator\0" /* 1069 */ - "divisor\0" /* 1079 */ - "divideBounds"; /* 1087 */ + "getDivTypeFromTypes\0" /* 212 */ + "modulo\0" /* 232 */ + "divisorMin\0" /* 239 */ + "divisorMax\0" /* 250 */ + "getMaxModuloMagnitude\0" /* 261 */ + "type\0" /* 283 */ + "getIntegerBounds\0" /* 288 */ + "getIntegerIntervals\0" /* 305 */ + "add\0" /* 325 */ + "arrayUnion\0" /* 329 */ + "tryArrayUnion\0" /* 340 */ + "subtract\0" /* 354 */ + "multiply\0" /* 363 */ + "shiftLeft\0" /* 372 */ + "shiftRight\0" /* 382 */ + "optimizeScalarType\0" /* 393 */ + "getNonNegativeIntegerBounds\0" /* 412 */ + "leftNumberType\0" /* 440 */ + "rightNumberType\0" /* 455 */ + "computeBitwiseAndRange\0" /* 471 */ + "computeBitwiseOrXorRange\0" /* 494 */ + "value\0" /* 519 */ + "allBitsMask\0" /* 525 */ + "operator\0" /* 537 */ + "resolveCommonMath\0" /* 546 */ + "negateNumber\0" /* 564 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 577 */ + "toIntegerOperand\0" /* 609 */ + "getTypeFromValue\0" /* 626 */ + "getNeverType\0" /* 643 */ + "negate\0" /* 656 */ + "numeratorType\0" /* 663 */ + "divisorType\0" /* 677 */ + "intDivide\0" /* 689 */ + "getIntervals\0" /* 699 */ + "numeratorMin\0" /* 712 */ + "numeratorMax\0" /* 725 */ + "divideIntervals\0" /* 738 */ + "numerator\0" /* 754 */ + "divisor\0" /* 764 */ + "divideBounds"; /* 772 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* concat $left */ reg::packed(23, 0, 5), /* concat $right */ @@ -131,26 +109,20 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 5), /* divide $rightType */ reg::packed(29, 0, 40), /* divide $phpVersion */ reg::packed(63, 0, 5), /* divide return */ - reg::packed(105, 0, 5), /* getIntegerDivisionFloatType $leftType */ - reg::packed(114, 0, 5), /* getIntegerDivisionFloatType $rightType */ - reg::packed(29, 0, 40), /* getIntegerDivisionFloatType $phpVersion */ - reg::packed(63, MAY_BE_NULL, 5), /* getIntegerDivisionFloatType return */ - reg::packed(240, 0, 5), /* replaceFloatType $type */ - reg::packed(245, 0, 5), /* replaceFloatType $floatType */ - reg::packed(63, 0, 5), /* replaceFloatType return */ reg::packed(105, 0, 5), /* getDivTypeFromTypes $leftType */ reg::packed(114, 0, 5), /* getDivTypeFromTypes $rightType */ + reg::packed(29, 0, 40), /* getDivTypeFromTypes $phpVersion */ reg::packed(63, 0, 5), /* getDivTypeFromTypes return */ reg::packed(105, 0, 5), /* modulo $leftType */ reg::packed(114, 0, 5), /* modulo $rightType */ reg::packed(29, 0, 40), /* modulo $phpVersion */ reg::packed(63, 0, 5), /* modulo return */ - reg::packed(299, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ - reg::packed(310, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ + reg::packed(239, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ + reg::packed(250, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude return */ - reg::packed(240, 0, 5), /* getIntegerBounds $type */ + reg::packed(283, 0, 5), /* getIntegerBounds $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerBounds return */ - reg::packed(240, 0, 5), /* getIntegerIntervals $type */ + reg::packed(283, 0, 5), /* getIntegerIntervals $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerIntervals return */ reg::packed(105, 0, 5), /* add $leftType */ reg::packed(114, 0, 5), /* add $rightType */ @@ -179,80 +151,48 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 5), /* shiftRight $rightType */ reg::packed(29, 0, 40), /* shiftRight $phpVersion */ reg::packed(63, 0, 5), /* shiftRight return */ - reg::packed(240, 0, 5), /* optimizeScalarType $type */ + reg::packed(283, 0, 5), /* optimizeScalarType $type */ reg::packed(63, 0, 5), /* optimizeScalarType return */ - reg::packed(240, 0, 5), /* getNonNegativeIntegerBounds $type */ + reg::packed(283, 0, 5), /* getNonNegativeIntegerBounds $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ - reg::packed(495, 0, 5), /* computeBitwiseAndRange $leftNumberType */ - reg::packed(510, 0, 5), /* computeBitwiseAndRange $rightNumberType */ + reg::packed(440, 0, 5), /* computeBitwiseAndRange $leftNumberType */ + reg::packed(455, 0, 5), /* computeBitwiseAndRange $rightNumberType */ reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseAndRange return */ - reg::packed(495, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ - reg::packed(510, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ + reg::packed(440, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ + reg::packed(455, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseOrXorRange return */ - reg::packed(574, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(519, MAY_BE_LONG), /* allBitsMask $value */ reg::packed(63, MAY_BE_LONG), /* allBitsMask return */ - reg::packed(592, MAY_BE_STRING), /* resolveCommonMath $operator */ + reg::packed(537, MAY_BE_STRING), /* resolveCommonMath $operator */ reg::packed(105, 0, 5), /* resolveCommonMath $leftType */ reg::packed(114, 0, 5), /* resolveCommonMath $rightType */ + reg::packed(29, 0, 40), /* resolveCommonMath $phpVersion */ reg::packed(63, 0, 5), /* resolveCommonMath return */ - reg::packed(619, 0, 5), /* integerRangeMath $range */ - reg::packed(592, MAY_BE_STRING), /* integerRangeMath $operator */ - reg::packed(625, 0, 5), /* integerRangeMath $operand */ - reg::packed(63, 0, 5), /* integerRangeMath return */ - reg::packed(574, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(650, MAY_BE_LONG), /* shiftLeftOverflows $shift */ - reg::packed(63, MAY_BE_BOOL), /* shiftLeftOverflows return */ - reg::packed(240, 0, 5), /* negateNumber $type */ + reg::packed(283, 0, 5), /* negateNumber $type */ reg::packed(63, 0, 5), /* negateNumber return */ - reg::packed(574, MAY_BE_DOUBLE), /* toIntBound $value */ - reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ - reg::packed(240, 0, 699), /* toIntegerOperand $type */ + reg::packed(283, 0, 577), /* toIntegerOperand $type */ reg::packed(63, MAY_BE_LONG), /* toIntegerOperand return */ - reg::packed(574, 0), /* getTypeFromValue $value */ + reg::packed(519, 0), /* getTypeFromValue $value */ reg::packed(63, 0, 5), /* getTypeFromValue return */ - reg::packed(592, MAY_BE_STRING), /* floatRangeMath $operator */ - reg::packed(105, 0, 5), /* floatRangeMath $leftType */ - reg::packed(114, 0, 5), /* floatRangeMath $rightType */ - reg::packed(29, 0, 40), /* floatRangeMath $phpVersion */ - reg::packed(63, MAY_BE_NULL, 5), /* floatRangeMath return */ - reg::packed(780, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ - reg::packed(63, MAY_BE_ARRAY), /* splitIntegerDivisor return */ - reg::packed(240, 0, 5), /* getFloatIntervalMembers $type */ - reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getFloatIntervalMembers return */ - reg::packed(833, 0, 843), /* isPlainFloat $numberSet */ - reg::packed(63, MAY_BE_BOOL), /* isPlainFloat return */ - reg::packed(592, MAY_BE_STRING), /* floatIntervalMath $operator */ - reg::packed(0, MAY_BE_ARRAY), /* floatIntervalMath $left */ - reg::packed(23, MAY_BE_ARRAY), /* floatIntervalMath $right */ - reg::packed(29, 0, 40), /* floatIntervalMath $phpVersion */ - reg::packed(63, 0, 5), /* floatIntervalMath return */ - reg::packed(592, MAY_BE_STRING), /* calculateFloat $operator */ - reg::packed(0, MAY_BE_DOUBLE), /* calculateFloat $left */ - reg::packed(23, MAY_BE_DOUBLE), /* calculateFloat $right */ - reg::packed(63, MAY_BE_DOUBLE), /* calculateFloat return */ - reg::packed(912, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ - reg::packed(918, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ - reg::packed(922, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ - reg::packed(63, MAY_BE_ARRAY), /* getFloatBoundWithInnerNeighbour return */ reg::packed(105, 0, 5), /* getNeverType $leftType */ reg::packed(114, 0, 5), /* getNeverType $rightType */ reg::packed(63, 0, 5), /* getNeverType return */ - reg::packed(240, 0, 5), /* negate $type */ + reg::packed(283, 0, 5), /* negate $type */ reg::packed(29, 0, 40), /* negate $phpVersion */ reg::packed(63, 0, 5), /* negate return */ - reg::packed(978, 0, 5), /* intDivide $numeratorType */ - reg::packed(992, 0, 5), /* intDivide $divisorType */ + reg::packed(663, 0, 5), /* intDivide $numeratorType */ + reg::packed(677, 0, 5), /* intDivide $divisorType */ reg::packed(29, 0, 40), /* intDivide $phpVersion */ reg::packed(63, 0, 5), /* intDivide return */ - reg::packed(240, 0, 5), /* getIntervals $type */ + reg::packed(283, 0, 5), /* getIntervals $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntervals return */ - reg::packed(1027, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ - reg::packed(1040, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ - reg::packed(299, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ - reg::packed(310, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ + reg::packed(712, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ + reg::packed(725, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ + reg::packed(239, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ + reg::packed(250, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ reg::packed(63, 0, 5), /* divideIntervals return */ - reg::packed(1069, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ - reg::packed(1079, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ + reg::packed(754, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ + reg::packed(764, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ reg::packed(63, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds return */ }; using Sig = reg::Sig; @@ -267,45 +207,33 @@ inline constexpr sigtab::Sig bitwiseOr = { { 135 /* bitwiseOr */, 3, 12, 3, 15, inline constexpr sigtab::Sig bitwiseXor = { { 145 /* bitwiseXor */, 3, 16, 3, 19, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig getFiniteOrConstantScalarTypes = { { 174 /* getFiniteOrConstantScalarTypes */, 3, 20, 3, 23, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig divide = { { 205 /* divide */, 3, 24, 3, 27, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerDivisionFloatType = { { 212 /* getIntegerDivisionFloatType */, 3, 28, 3, 31, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig replaceFloatType = { { 255 /* replaceFloatType */, 2, 32, 2, 34, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getDivTypeFromTypes = { { 272 /* getDivTypeFromTypes */, 2, 35, 2, 37, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig modulo = { { 292 /* modulo */, 3, 38, 3, 41, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 321 /* getMaxModuloMagnitude */, 2, 42, 2, 44, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerBounds = { { 343 /* getIntegerBounds */, 1, 45, 1, 46, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerIntervals = { { 360 /* getIntegerIntervals */, 1, 47, 1, 48, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig add = { { 380 /* add */, 3, 49, 3, 52, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 384 /* arrayUnion */, 3, 53, 3, 56, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 395 /* tryArrayUnion */, 2, 57, 2, 59, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig subtract = { { 409 /* subtract */, 3, 60, 3, 63, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig multiply = { { 418 /* multiply */, 3, 64, 3, 67, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 427 /* shiftLeft */, 3, 68, 3, 71, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftRight = { { 437 /* shiftRight */, 3, 72, 3, 75, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 448 /* optimizeScalarType */, 1, 76, 1, 77, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 467 /* getNonNegativeIntegerBounds */, 1, 78, 1, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseAndRange = { { 526 /* computeBitwiseAndRange */, 2, 80, 2, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 549 /* computeBitwiseOrXorRange */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig allBitsMask = { { 580 /* allBitsMask */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 601 /* resolveCommonMath */, 3, 88, 3, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig integerRangeMath = { { 633 /* integerRangeMath */, 3, 92, 3, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 656 /* shiftLeftOverflows */, 2, 96, 2, 98, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negateNumber = { { 675 /* negateNumber */, 1, 99, 1, 100, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntBound = { { 688 /* toIntBound */, 1, 101, 1, 102, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntegerOperand = { { 731 /* toIntegerOperand */, 1, 103, 1, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 748 /* getTypeFromValue */, 1, 105, 1, 106, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig floatRangeMath = { { 765 /* floatRangeMath */, 4, 107, 4, 111, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig splitIntegerDivisor = { { 789 /* splitIntegerDivisor */, 1, 112, 1, 113, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatIntervalMembers = { { 809 /* getFloatIntervalMembers */, 1, 114, 1, 115, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isPlainFloat = { { 866 /* isPlainFloat */, 1, 116, 1, 117, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig floatIntervalMath = { { 879 /* floatIntervalMath */, 4, 118, 4, 122, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloat = { { 897 /* calculateFloat */, 3, 123, 3, 126, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 926 /* getFloatBoundWithInnerNeighbour */, 3, 127, 3, 130, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 958 /* getNeverType */, 2, 131, 2, 133, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 971 /* negate */, 2, 134, 2, 136, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 1004 /* intDivide */, 3, 137, 3, 140, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntervals = { { 1014 /* getIntervals */, 1, 141, 1, 142, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntervals = { { 1053 /* divideIntervals */, 4, 143, 4, 147, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBounds = { { 1087 /* divideBounds */, 2, 148, 2, 150, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getDivTypeFromTypes = { { 212 /* getDivTypeFromTypes */, 3, 28, 3, 31, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig modulo = { { 232 /* modulo */, 3, 32, 3, 35, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 261 /* getMaxModuloMagnitude */, 2, 36, 2, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerBounds = { { 288 /* getIntegerBounds */, 1, 39, 1, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerIntervals = { { 305 /* getIntegerIntervals */, 1, 41, 1, 42, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig add = { { 325 /* add */, 3, 43, 3, 46, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 329 /* arrayUnion */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 340 /* tryArrayUnion */, 2, 51, 2, 53, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig subtract = { { 354 /* subtract */, 3, 54, 3, 57, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig multiply = { { 363 /* multiply */, 3, 58, 3, 61, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 372 /* shiftLeft */, 3, 62, 3, 65, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftRight = { { 382 /* shiftRight */, 3, 66, 3, 69, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig optimizeScalarType = { { 393 /* optimizeScalarType */, 1, 70, 1, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 412 /* getNonNegativeIntegerBounds */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseAndRange = { { 471 /* computeBitwiseAndRange */, 2, 74, 2, 76, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 494 /* computeBitwiseOrXorRange */, 2, 77, 2, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig allBitsMask = { { 525 /* allBitsMask */, 1, 80, 1, 81, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolveCommonMath = { { 546 /* resolveCommonMath */, 4, 82, 4, 86, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negateNumber = { { 564 /* negateNumber */, 1, 87, 1, 88, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntegerOperand = { { 609 /* toIntegerOperand */, 1, 89, 1, 90, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 626 /* getTypeFromValue */, 1, 91, 1, 92, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNeverType = { { 643 /* getNeverType */, 2, 93, 2, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negate = { { 656 /* negate */, 2, 96, 2, 98, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 689 /* intDivide */, 3, 99, 3, 102, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntervals = { { 699 /* getIntervals */, 1, 103, 1, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntervals = { { 738 /* divideIntervals */, 4, 105, 4, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBounds = { { 772 /* divideBounds */, 2, 110, 2, 112, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper From a26acb4fb2a151232084590d8ece40b8ff98cfe2 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 19:29:04 +0000 Subject: [PATCH 3/4] Remove an extra blank line from the baseline Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9e58664acfc..aaf043221ee 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1513,7 +1513,6 @@ parameters: count: 1 path: src/Type/OperatorTypeHelper.php - - rawMessage: Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead. identifier: phpstanApi.runtimeReflection From 641177d36df384ddfd1a42e90358a13f9b2f4160 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 19:51:04 +0000 Subject: [PATCH 4/4] Test loose comparisons under each PHP version's rules The == narrowing follows the analysed PHP version: PHP 7 compares a non-numeric string with a number as a number, has no Stringable, and BcMath\Number compares as its number since PHP 8.4. The nsrt comparisons now run inside PHP_VERSION_ID guards, so their results no longer depend on the PHP version running the tests. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- tests/PHPStan/Analyser/TypeSpecifierTest.php | 3 +- tests/PHPStan/Analyser/nsrt/equal-narrow.php | 60 ++++++++++++++++++- tests/PHPStan/Analyser/nsrt/equal.php | 7 ++- .../Analyser/nsrt/integer-range-types.php | 10 +++- .../Analyser/nsrt/non-empty-string.php | 8 ++- ...sibleCheckTypeMethodCallRuleEqualsTest.php | 14 ++++- 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index e440c2e78bf..d1d9fcfdcf6 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1321,7 +1321,8 @@ public static function dataCondition(): iterable ), [ '$float' => 'float<-9.223372036854776E+18, 9.223372036854776E+18>|int|numeric-string', - '(int) $float' => '\'-INF\'|\'INF\'|bool|float|int|object|resource|numeric-string|null', + // PHP 7 compares any string with a number as the number the string starts with + '(int) $float' => PHP_VERSION_ID < 80000 ? 'bool|float|int|object|resource|string|null' : '\'-INF\'|\'INF\'|bool|float|int|object|resource|numeric-string|null', ], [], ], diff --git a/tests/PHPStan/Analyser/nsrt/equal-narrow.php b/tests/PHPStan/Analyser/nsrt/equal-narrow.php index df0aafc7a46..679feb7d522 100644 --- a/tests/PHPStan/Analyser/nsrt/equal-narrow.php +++ b/tests/PHPStan/Analyser/nsrt/equal-narrow.php @@ -122,7 +122,35 @@ function doTrue($x, $y, $z): void */ function doZero($x, $y, $z): void { - // PHP 7.x/8.x compatibility: Keep zero in both cases + if (PHP_VERSION_ID < 80000) { + // PHP 7 compares a non-numeric string with a number as a number + if ($x == 0) { + assertType("0|0.0|''|'0'|'x'|GMP|SimpleXMLElement|false|null", $x); + } else { + assertType("1|'x'|array{}|object|true", $x); + } + if (0 != $x) { + assertType("1|'x'|array{}|object|true", $x); + } else { + assertType("0|0.0|''|'0'|'x'|GMP|SimpleXMLElement|false|null", $x); + } + + if ($y == 0) { + assertType('0|string|null', $y); + } else { + assertType('int|int<1, max>|non-falsy-string', $y); + } + + if ($z == 0) { + assertType('mixed', $z); + } else { + assertType("mixed~(0|0.0|''|'0'|false|null)", $z); + } + + return; + } + + // PHP 8.0 and later, BcMath\Number compares as its number since PHP 8.4 if ($x == 0) { assertType("0|0.0|'0'|BcMath\Number|GMP|SimpleXMLElement|false|null", $x); } else { @@ -154,7 +182,35 @@ function doZero($x, $y, $z): void */ function doEmptyString($x, $y, $z): void { - // PHP 7.x/8.x compatibility: Keep zero in both cases + if (PHP_VERSION_ID < 80000) { + // PHP 7 compares an empty string with a number as 0, and has no Stringable to describe objects with __toString() + if ($x == '') { + assertType("0|0.0|''|object|false|null", $x); + } else { + assertType("1|'0'|'x'|array{}|object|true", $x); + } + if ('' != $x) { + assertType("1|'0'|'x'|array{}|object|true", $x); + } else { + assertType("0|0.0|''|object|false|null", $x); + } + + if ($y == '') { + assertType("0|''|null", $y); + } else { + assertType('int|int<1, max>|non-empty-string', $y); + } + + if ($z == '') { + assertType('mixed', $z); + } else { + assertType("mixed~(0|0.0|''|false|null)", $z); + } + + return; + } + + // PHP 8.0 and later if ($x == '') { assertType("''|GMP|Stringable|false|null", $x); } else { diff --git a/tests/PHPStan/Analyser/nsrt/equal.php b/tests/PHPStan/Analyser/nsrt/equal.php index d9f9f407b42..06e24d9020e 100644 --- a/tests/PHPStan/Analyser/nsrt/equal.php +++ b/tests/PHPStan/Analyser/nsrt/equal.php @@ -156,7 +156,12 @@ public function doFoo(string $a, float $c): void assertType('true', 1 == 1); assertType('false', 1 == 0); - assertType('false', $c == 'a'); + if (PHP_VERSION_ID < 80000) { + // PHP 7 compares a non-numeric string with a float as 0.0 + assertType('bool', $c == 'a'); + } else { + assertType('false', $c == 'a'); + } assertType('bool', $c == 1); assertType('bool', $c == 1.2); assertType('true', 1.2 == 1.2); diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index fcbd7757141..67b478d5297 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -443,8 +443,14 @@ public function zeroIssues($positive, $negative) } function subtract($m) { - if ($m != 0) { - assertType("mixed~(0|0.0|'0'|false|null)", $m); // could be "mixed~(0|0.0|''|'0'|false|null)" + if (PHP_VERSION_ID < 80000) { + if ($m != 0) { + // PHP 7 compares an empty string with a number as 0 + assertType("mixed~(0|0.0|''|'0'|false|null)", $m); + assertType('int', (int) $m); + } + } elseif ($m != 0) { + assertType("mixed~(0|0.0|'0'|false|null)", $m); assertType('int', (int) $m); } if ($m !== 0) { diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index c8031310aee..4abb588f617 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -431,7 +431,13 @@ function subtract($m) { assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $m); assertType('non-falsy-string', (string) $m); } - if ($m != '') { + if (PHP_VERSION_ID < 80000) { + if ($m != '') { + // PHP 7 compares an empty string with a number as 0 + assertType("mixed~(0|0.0|''|false|null)", $m); + assertType('non-empty-string', (string) $m); + } + } elseif ($m != '') { assertType("mixed~(''|false|null)", $m); assertType('non-empty-string', (string) $m); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php index 48b1eb768d6..2058c69b17f 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php @@ -4,6 +4,8 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function array_merge; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -28,7 +30,7 @@ public function getRule(): Rule public function testRule(): void { - $this->analyse([__DIR__ . '/data/impossible-method-call.php'], [ + $errors = [ [ 'Call to method PHPStan\Tests\AssertionClass::assertString() with string will always evaluate to true.', 14, @@ -97,10 +99,15 @@ public function testRule(): void 'Call to method ImpossibleMethodCall\Foo::isNotSame() with array{1, 3} and array{1, 3} will always evaluate to false.', 122, ], - [ + ]; + // PHP 7 has no Stringable to tell which objects can equal a string + if (PHP_VERSION_ID >= 80000) { + $errors[] = [ 'Call to method ImpossibleMethodCall\Foo::isSame() with \'1\' and stdClass will always evaluate to false.', 133, - ], + ]; + } + $errors = array_merge($errors, [ [ 'Call to method ImpossibleMethodCall\Foo::isSame() with array{\'a\', \'b\'} and array{1, 2} will always evaluate to false.', 139, @@ -131,6 +138,7 @@ public function testRule(): void 'Remove remaining cases below this one and this error will disappear too.', ], ]); + $this->analyse([__DIR__ . '/data/impossible-method-call.php'], $errors); } public static function getAdditionalConfigFiles(): array