From 9262ca87136a244ce187dcf88fbc1779a127afa5 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 16:33:48 +0000 Subject: [PATCH 01/14] Compare constant scalars according to the configured PHP version Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Php/PhpVersion.php | 6 + .../InitializerExprTypeResolver.php | 3 +- src/Type/LooseComparisonHelper.php | 197 ++++++++++++++---- src/Type/Traits/ConstantScalarTypeTrait.php | 6 +- .../data/loose-const-comparison-php7.php | 53 +++++ .../data/loose-const-comparison-php8.php | 53 +++++ turbo-ext/src/InitializerExprTypeResolver.cpp | 7 +- turbo-ext/src/TypeTraits.cpp | 10 +- 8 files changed, 287 insertions(+), 48 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6667a5916d9..ad83cfc05ed 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -324,6 +324,12 @@ public function nonNumericStringAndIntegerIsFalseOnLooseComparison(): bool return $this->versionId >= 80000; } + // see https://wiki.php.net/rfc/saner-numeric-strings + public function allowsTrailingWhitespaceInNumericStrings(): bool + { + return $this->versionId >= 80000; + } + public function supportsCallableInstanceMethods(): bool { return $this->versionId < 80000; diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index abf5384f2a4..42451466f2e 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -79,6 +79,7 @@ use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NonexistentParentClassType; @@ -1329,7 +1330,7 @@ public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallb $resultTypes = []; foreach ($leftValues as $leftValue) { foreach ($rightValues as $rightValue) { - $resultType = $this->getTypeFromValue($leftValue <=> $rightValue); + $resultType = new ConstantIntegerType(LooseComparisonHelper::compareValues($leftValue, $rightValue, $this->phpVersion) ?? 1); $resultTypes[] = $resultType; } } diff --git a/src/Type/LooseComparisonHelper.php b/src/Type/LooseComparisonHelper.php index 8742cd9f8eb..6a5b7da3179 100644 --- a/src/Type/LooseComparisonHelper.php +++ b/src/Type/LooseComparisonHelper.php @@ -3,69 +3,186 @@ namespace PHPStan\Type; use PHPStan\Php\PhpVersion; +use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ReferencedByTurboExtension; use PHPStan\Type\Constant\ConstantBooleanType; +use function is_bool; use function is_float; +use function is_infinite; use function is_nan; +use function is_numeric; +use function is_string; +use function preg_match; +use function strcmp; +/** + * Evaluates PHP's comparison of two scalar values (`==`, `<`, `<=`, `<=>`) the way + * the configured PHP version does it, independently of the PHP version PHPStan runs on. + */ #[ReferencedByTurboExtension(key: 'looseComparisonHelper')] final class LooseComparisonHelper { + private const WHITESPACE = " \t\n\r\v\f"; + + // the integer digits are captured only for a number without a fraction, the exponent separately + private const NUMBER = '([+-]?(?:\d+\.\d*|\.\d+|(\d+))([eE][+-]?\d+)?)'; + public static function compareConstantScalars(ConstantScalarType $leftType, ConstantScalarType $rightType, PhpVersion $phpVersion): BooleanType { - if ($phpVersion->castsNumbersToStringsOnLooseComparison()) { - $isNumber = new UnionType([ - new IntegerType(), - new FloatType(), - ]); - - if ($leftType->isString()->yes() && $leftType->isNumericString()->no() && $isNumber->isSuperTypeOf($rightType)->yes()) { - if (self::isNan($rightType->getValue())) { - return new ConstantBooleanType(false); - } + return new ConstantBooleanType(self::compareValues($leftType->getValue(), $rightType->getValue(), $phpVersion) === 0); + } - $stringValue = (string) $rightType->getValue(); - return new ConstantBooleanType($stringValue === $leftType->getValue()); + /** + * Mirrors zend_compare(): -1, 0 or 1, or null when the values are uncomparable + * because one of them is NAN. Such a pair is never equal, smaller or greater, + * and `<=>` returns 1 for it. + */ + public static function compareValues(bool|float|int|string|null $left, bool|float|int|string|null $right, PhpVersion $phpVersion): ?int + { + if (!is_string($left)) { + if (!is_string($right)) { + return self::compareNonStrings($left, $right); } - if ($rightType->isString()->yes() && $rightType->isNumericString()->no() && $isNumber->isSuperTypeOf($leftType)->yes()) { - if (self::isNan($leftType->getValue())) { - return new ConstantBooleanType(false); - } - $stringValue = (string) $leftType->getValue(); - return new ConstantBooleanType($stringValue === $rightType->getValue()); - } - } else { - if ($leftType->isString()->yes() && $leftType->isNumericString()->no() && $rightType->isFloat()->yes()) { - $numericPart = (float) $leftType->getValue(); - return new ConstantBooleanType($numericPart === $rightType->getValue()); - } - if ($rightType->isString()->yes() && $rightType->isNumericString()->no() && $leftType->isFloat()->yes()) { - $numericPart = (float) $rightType->getValue(); - return new ConstantBooleanType($numericPart === $leftType->getValue()); - } - if ($leftType->isString()->yes() && $leftType->isNumericString()->no() && $rightType->isInteger()->yes()) { - $numericPart = (int) $leftType->getValue(); - return new ConstantBooleanType($numericPart === $rightType->getValue()); - } - if ($rightType->isString()->yes() && $rightType->isNumericString()->no() && $leftType->isInteger()->yes()) { - $numericPart = (int) $rightType->getValue(); - return new ConstantBooleanType($numericPart === $leftType->getValue()); + $result = self::compareValues($right, $left, $phpVersion); + return $result === null ? null : -$result; + } + + if (is_string($right)) { + return self::compareStrings($left, $right, $phpVersion); + } + + if ($right === null || is_bool($right)) { + // null and booleans compare with strings the same way in every PHP version + return $left <=> $right; + } + + if (self::isNan($right)) { + return null; + } + + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + // the string's leading number, or 0 when there is none + return self::compareNonStrings(self::parseNumericPrefix($left) ?? 0, $right); + } + + $number = self::parseNumericString($left, $phpVersion); + if ($number !== null) { + return self::compareNonStrings($number[0], $right); + } + + return self::compareBytes($left, (string) $right); + } + + private static function compareNonStrings(bool|float|int|null $left, bool|float|int|null $right): ?int + { + if (self::isNan($left) || self::isNan($right)) { + if ($left === null || is_bool($left) || $right === null || is_bool($right)) { + // null and booleans compare with NAN as with true + return $left <=> $right; } + + return null; } - // @phpstan-ignore equal.notAllowed - return new ConstantBooleanType($leftType->getValue() == $rightType->getValue()); // phpcs:ignore + return $left <=> $right; } /** - * NAN is never cast to a string: it compares equal to nothing, not even to - * 'NAN' (and casting it warns since PHP 8.5). + * Mirrors zendi_smart_strcmp(): numeric strings compare as numbers, any other pair byte by byte. */ - private static function isNan(bool|float|int|string|null $value): bool + private static function compareStrings(string $left, string $right, PhpVersion $phpVersion): int + { + $leftNumber = self::parseNumericString($left, $phpVersion); + $rightNumber = self::parseNumericString($right, $phpVersion); + if ($leftNumber === null || $rightNumber === null) { + return self::compareBytes($left, $right); + } + + [$leftValue, $leftOverflow] = $leftNumber; + [$rightValue, $rightOverflow] = $rightNumber; + + if ($leftOverflow !== 0 && $leftOverflow === $rightOverflow && $leftValue === $rightValue) { + // integers that overflowed to the same side lose the precision needed to compare them + return self::compareBytes($left, $right); + } + + if (is_float($leftValue) || is_float($rightValue)) { + if (!is_float($leftValue)) { + if ($rightOverflow !== 0) { + return -$rightOverflow; + } + } elseif (!is_float($rightValue)) { + if ($leftOverflow !== 0) { + return $leftOverflow; + } + } elseif ($leftValue === $rightValue && is_infinite($leftValue)) { + return self::compareBytes($left, $right); + } + } + + return $leftValue <=> $rightValue; + } + + private static function isNan(bool|float|int|null $value): bool { return is_float($value) && is_nan($value); } + private static function compareBytes(string $left, string $right): int + { + return strcmp($left, $right) <=> 0; + } + + /** + * The value of a numeric string and the direction an integer overflowed in (1, -1, or 0). + * Leading whitespace is allowed in every PHP version, trailing whitespace since PHP 8.0. + * + * @return array{int|float, int}|null + */ + private static function parseNumericString(string $value, PhpVersion $phpVersion): ?array + { + $pattern = $phpVersion->allowsTrailingWhitespaceInNumericStrings() + ? '/^[' . self::WHITESPACE . ']*' . self::NUMBER . '[' . self::WHITESPACE . ']*$/D' + : '/^[' . self::WHITESPACE . ']*' . self::NUMBER . '$/D'; + if (preg_match($pattern, $value, $matches) !== 1) { + return null; + } + + return self::toNumber($matches); + } + + /** + * The number a string starts with, as PHP 7 compares a string with a number. + */ + private static function parseNumericPrefix(string $value): int|float|null + { + if (preg_match('/^[' . self::WHITESPACE . ']*' . self::NUMBER . '/', $value, $matches) !== 1) { + return null; + } + + return self::toNumber($matches)[0]; + } + + /** + * @param array $matches + * @return array{int|float, int} + */ + private static function toNumber(array $matches): array + { + $numericString = $matches[1]; + if (!is_numeric($numericString)) { + throw new ShouldNotHappenException(); + } + + $number = +$numericString; + $isIntegerSyntax = ($matches[2] ?? '') !== '' && ($matches[3] ?? '') === ''; + if ($isIntegerSyntax && is_float($number)) { + return [$number, $number < 0 ? -1 : 1]; + } + + return [$number, 0]; + } + } diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index f4585126225..23509fde477 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -77,7 +77,8 @@ public function equals(Type $type): bool public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { - return TrinaryLogic::createFromBoolean($this->value < $otherType->getValue()); + $comparison = LooseComparisonHelper::compareValues($this->value, $otherType->getValue(), $phpVersion); + return TrinaryLogic::createFromBoolean($comparison !== null && $comparison < 0); } if ($otherType instanceof CompoundType) { @@ -90,7 +91,8 @@ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryL public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { - return TrinaryLogic::createFromBoolean($this->value <= $otherType->getValue()); + $comparison = LooseComparisonHelper::compareValues($this->value, $otherType->getValue(), $phpVersion); + return TrinaryLogic::createFromBoolean($comparison !== null && $comparison <= 0); } if ($otherType instanceof CompoundType) { diff --git a/tests/PHPStan/Analyser/data/loose-const-comparison-php7.php b/tests/PHPStan/Analyser/data/loose-const-comparison-php7.php index 9a0c544df1d..d2710f7bedc 100644 --- a/tests/PHPStan/Analyser/data/loose-const-comparison-php7.php +++ b/tests/PHPStan/Analyser/data/loose-const-comparison-php7.php @@ -18,3 +18,56 @@ function doFoo() { assertType('false', 42.1 == "42.0foo"); assertType('true', 42.0 == "42.0foo"); } + +function comparisons(): void +{ + assertType('true', 'abc' < 1); + assertType('true', 'abc' <= 0); + assertType('false', 'abc' > 1); + assertType('true', 'abc' >= 0); + assertType('-1', 'abc' <=> 1); + assertType('1', 1 <=> 'abc'); + assertType('0', '1abc' <=> 1); + assertType('0', 1.5 <=> '1.5abc'); + assertType('false', '1 ' == '1'); + assertType('true', '1 ' == 1); + assertType('false', "1\n" == '1'); + assertType('true', ' 1' == '1'); + assertType('true', '10 ' < '9'); + assertType('-1', '10 ' <=> '9'); + assertType('false', 1 == '1.5abc'); + assertType('true', 1000 == '1e3abc'); + assertType('true', '1e3' == '1000'); + assertType('true', '0.1' == '0.10'); + assertType('true', 'abc' == 0); + assertType('false', null == '0'); + assertType('true', null < -1); + assertType('-1', null <=> -1); + assertType('-1', null <=> NAN); + assertType('false', null == NAN); + assertType('0', true <=> 'abc'); + assertType('false', NAN == NAN); + assertType('true', NAN != NAN); + assertType('false', NAN < 1); + assertType('false', 1 < NAN); + assertType('false', NAN <= NAN); + assertType('1', NAN <=> 1); + assertType('1', 1 <=> NAN); + assertType('1', NAN <=> NAN); + assertType('false', NAN == 'NAN'); + assertType('1', NAN <=> 'abc'); + assertType('true', NAN == true); + assertType('false', INF == 'INF'); + assertType('1', INF <=> 'INF'); + assertType('true', INF == INF); + assertType('true', -INF < PHP_INT_MIN); + assertType('true', INF > PHP_INT_MAX); + assertType('true', 9223372036854775807 == 9223372036854775807.0); + assertType('false', '9223372036854775808' == '9223372036854775809'); + assertType('-1', '9223372036854775808' <=> '9223372036854775809'); + assertType('false', '1e1000' == '1e1001'); + assertType('1', '9223372036854775808' <=> 1); + assertType('true', -0.0 == 0); + assertType('0', -0.0 <=> 0.0); + assertType('true', '-0' == '0'); +} diff --git a/tests/PHPStan/Analyser/data/loose-const-comparison-php8.php b/tests/PHPStan/Analyser/data/loose-const-comparison-php8.php index a970e1d12a1..08f33a73032 100644 --- a/tests/PHPStan/Analyser/data/loose-const-comparison-php8.php +++ b/tests/PHPStan/Analyser/data/loose-const-comparison-php8.php @@ -18,3 +18,56 @@ function doFoo() { assertType('false', 42.1 == "42.0foo"); assertType('false', 42.0 == "42.0foo"); } + +function comparisons(): void +{ + assertType('false', 'abc' < 1); + assertType('false', 'abc' <= 0); + assertType('true', 'abc' > 1); + assertType('true', 'abc' >= 0); + assertType('1', 'abc' <=> 1); + assertType('-1', 1 <=> 'abc'); + assertType('1', '1abc' <=> 1); + assertType('-1', 1.5 <=> '1.5abc'); + assertType('true', '1 ' == '1'); + assertType('true', '1 ' == 1); + assertType('true', "1\n" == '1'); + assertType('true', ' 1' == '1'); + assertType('false', '10 ' < '9'); + assertType('1', '10 ' <=> '9'); + assertType('false', 1 == '1.5abc'); + assertType('false', 1000 == '1e3abc'); + assertType('true', '1e3' == '1000'); + assertType('true', '0.1' == '0.10'); + assertType('false', 'abc' == 0); + assertType('false', null == '0'); + assertType('true', null < -1); + assertType('-1', null <=> -1); + assertType('-1', null <=> NAN); + assertType('false', null == NAN); + assertType('0', true <=> 'abc'); + assertType('false', NAN == NAN); + assertType('true', NAN != NAN); + assertType('false', NAN < 1); + assertType('false', 1 < NAN); + assertType('false', NAN <= NAN); + assertType('1', NAN <=> 1); + assertType('1', 1 <=> NAN); + assertType('1', NAN <=> NAN); + assertType('false', NAN == 'NAN'); + assertType('1', NAN <=> 'abc'); + assertType('true', NAN == true); + assertType('true', INF == 'INF'); + assertType('0', INF <=> 'INF'); + assertType('true', INF == INF); + assertType('true', -INF < PHP_INT_MIN); + assertType('true', INF > PHP_INT_MAX); + assertType('true', 9223372036854775807 == 9223372036854775807.0); + assertType('false', '9223372036854775808' == '9223372036854775809'); + assertType('-1', '9223372036854775808' <=> '9223372036854775809'); + assertType('false', '1e1000' == '1e1001'); + assertType('1', '9223372036854775808' <=> 1); + assertType('true', -0.0 == 0); + assertType('0', -0.0 <=> 0.0); + assertType('true', '-0' == '0'); +} diff --git a/turbo-ext/src/InitializerExprTypeResolver.cpp b/turbo-ext/src/InitializerExprTypeResolver.cpp index a8dccb753ac..8bd42468bb0 100644 --- a/turbo-ext/src/InitializerExprTypeResolver.cpp +++ b/turbo-ext/src/InitializerExprTypeResolver.cpp @@ -1429,9 +1429,12 @@ class InitializerExprTypeResolver zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftValuesCount * rightValuesCount)); for (zv::ArrayEntry leftEntry : zv::ArrRef(leftValues.raw())) { for (zv::ArrayEntry rightEntry : zv::ArrRef(rightValues.raw())) { + /* LooseComparisonHelper::compareValues($leftValue, $rightValue, $this->phpVersion) ?? 1 */ + zv::Args args{leftEntry.value().raw(), rightEntry.value().raw(), prop(slots::phpVersion)}; + zv::Val compared = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("comparevalues"), 3, args); + if (UNEXPECTED(compared.isUndef())) return zv::Val(); zval comparison; - ZVAL_LONG(&comparison, zend_compare(leftEntry.value().raw(), rightEntry.value().raw())); - if (UNEXPECTED(EG(exception) != NULL)) return zv::Val(); + ZVAL_LONG(&comparison, Z_TYPE_P(compared.raw()) == IS_LONG ? Z_LVAL_P(compared.raw()) : 1); IETR_VAL(resultType, typeFromValue(&comparison)); resultTypes.push(std::move(resultType)); } diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index 58b93068057..53a76abee9a 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -1087,9 +1087,13 @@ static void constantScalarSmaller(INTERNAL_FUNCTION_PARAMETERS, bool orEqual) if (UNEXPECTED(selfValue == NULL)) RETURN_THROWS(); zv::Val otherValue = constantScalarGetValue(Z_OBJ_P(otherType)); if (UNEXPECTED(otherValue.isUndef())) RETURN_THROWS(); - int comparison = zend_compare(selfValue, otherValue.raw()); - if (UNEXPECTED(EG(exception))) RETURN_THROWS(); - PT_RETURN_TRINARY((orEqual ? comparison <= 0 : comparison < 0) ? PT_TRI_YES : PT_TRI_NO); + /* LooseComparisonHelper::compareValues($this->value, $otherType->getValue(), $phpVersion): + * the configured PHP version's comparison, not the runtime's zend_compare() */ + zv::Args args{selfValue, otherValue.raw(), phpVersion}; + zv::Val comparison = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("comparevalues"), 3, args); + if (UNEXPECTED(comparison.isUndef())) RETURN_THROWS(); + bool holds = Z_TYPE_P(comparison.raw()) == IS_LONG && (orEqual ? Z_LVAL_P(comparison.raw()) <= 0 : Z_LVAL_P(comparison.raw()) < 0); + PT_RETURN_TRINARY(holds ? PT_TRI_YES : PT_TRI_NO); } bool compound; From aea2d467f3af35a8ab4dab7e42f7bacc447444a0 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 17:44:12 +0000 Subject: [PATCH 02/14] Move operator type inference from InitializerExprTypeResolver to Type methods Each operator is now a Type method taking the right operand and the configured PhpVersion: arithmeticAdd(), arithmeticSubtract(), arithmeticMultiply(), arithmeticDivide(), arithmeticIntDivide(), arithmeticModulo(), arithmeticPower(), arithmeticNegate(), bitwiseAnd(), bitwiseOr(), bitwiseXor(), bitwiseShiftLeft(), bitwiseShiftRight(), stringConcatenate() and arrayUnion(). Their shared bodies live in OperatorTypeHelper (shadowed natively) through OperatorTypeTrait. exponentiate() is deprecated in favour of arithmeticPower(). InitializerExprTypeResolver calls the operator type-specifying extensions first and the Type method second, so an extension now also sees operations the resolver used to shortcut (BcMath\Number * null is an error now instead of 0). The inferred types are otherwise unchanged: they are byte-identical on every supported PHP version, with and without the turbo extension. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 128 +- .../InitializerExprTypeResolver.php | 1641 +--------- src/Type/Accessory/AccessoryArrayListType.php | 4 +- .../AccessoryDecimalIntegerStringType.php | 4 +- .../Accessory/AccessoryLiteralStringType.php | 4 +- .../AccessoryLowercaseStringType.php | 4 +- .../Accessory/AccessoryNonEmptyStringType.php | 4 +- .../Accessory/AccessoryNonFalsyStringType.php | 4 +- .../Accessory/AccessoryNumericStringType.php | 4 +- .../AccessoryUppercaseStringType.php | 4 +- src/Type/Accessory/HasMethodType.php | 4 +- src/Type/Accessory/HasOffsetType.php | 4 +- src/Type/Accessory/HasOffsetValueType.php | 4 +- src/Type/Accessory/HasPropertyType.php | 4 +- src/Type/Accessory/NonEmptyArrayType.php | 4 +- src/Type/Accessory/OversizedArrayType.php | 4 +- src/Type/BooleanType.php | 4 +- src/Type/CallableType.php | 4 +- src/Type/ClosureType.php | 4 +- src/Type/FloatType.php | 4 +- .../UnresolvedTemplateArgumentType.php | 7 +- src/Type/IntegerRangeType.php | 9 +- src/Type/IntegerType.php | 4 +- src/Type/IntersectionType.php | 6 +- src/Type/IterableType.php | 4 +- src/Type/MixedType.php | 4 +- src/Type/NeverType.php | 4 +- src/Type/NonexistentParentClassType.php | 4 +- src/Type/NullType.php | 4 +- src/Type/ObjectShapeType.php | 5 +- src/Type/ObjectType.php | 4 +- src/Type/ObjectWithoutClassType.php | 5 +- src/Type/OperatorTypeHelper.php | 1690 ++++++++++ .../Php/IntdivFunctionReturnTypeExtension.php | 113 +- src/Type/ResourceType.php | 4 +- src/Type/StaticType.php | 6 +- src/Type/StrictMixedType.php | 4 +- src/Type/StringType.php | 4 +- src/Type/Traits/ArrayTypeTrait.php | 5 +- src/Type/Traits/LateResolvableTypeTrait.php | 6 +- src/Type/Traits/OperatorTypeTrait.php | 88 + src/Type/Type.php | 54 +- src/Type/UnionType.php | 6 +- src/Type/VoidType.php | 4 +- tests/PHPStan/Analyser/nsrt/bcmath-number.php | 2 +- turbo-ext/src/AccessoryArrayListType.cpp | 6 +- .../src/AccessoryDecimalIntegerStringType.cpp | 8 +- turbo-ext/src/AccessoryLiteralStringType.cpp | 8 +- .../src/AccessoryLowercaseStringType.cpp | 8 +- turbo-ext/src/AccessoryNonEmptyStringType.cpp | 8 +- turbo-ext/src/AccessoryNonFalsyStringType.cpp | 8 +- turbo-ext/src/AccessoryNumericStringType.cpp | 8 +- .../src/AccessoryUppercaseStringType.cpp | 8 +- turbo-ext/src/BooleanType.cpp | 5 +- turbo-ext/src/CallableType.cpp | 4 +- turbo-ext/src/ClosureType.cpp | 4 +- turbo-ext/src/FloatType.cpp | 5 +- turbo-ext/src/HasMethodType.cpp | 6 +- turbo-ext/src/HasOffsetType.cpp | 6 +- turbo-ext/src/HasOffsetValueType.cpp | 6 +- turbo-ext/src/HasPropertyType.cpp | 6 +- turbo-ext/src/IetrSupport.h | 646 ++++ turbo-ext/src/InitializerExprTypeResolver.cpp | 2807 +---------------- turbo-ext/src/IntegerRangeType.cpp | 14 +- turbo-ext/src/IntegerType.cpp | 9 +- turbo-ext/src/IntersectionType.cpp | 10 +- turbo-ext/src/IterableType.cpp | 4 +- turbo-ext/src/MixedType.cpp | 8 +- turbo-ext/src/NeverType.cpp | 5 +- turbo-ext/src/NonEmptyArrayType.cpp | 2 +- turbo-ext/src/NonexistentParentClassType.cpp | 2 +- turbo-ext/src/NullType.cpp | 10 +- turbo-ext/src/ObjectShapeType.cpp | 6 +- turbo-ext/src/ObjectType.cpp | 11 +- turbo-ext/src/ObjectWithoutClassType.cpp | 6 +- turbo-ext/src/OperatorTypeHelper.cpp | 2362 ++++++++++++++ turbo-ext/src/OversizedArrayType.cpp | 8 +- turbo-ext/src/ResourceType.cpp | 6 +- turbo-ext/src/StaticType.cpp | 4 +- turbo-ext/src/StrictMixedType.cpp | 2 +- turbo-ext/src/StringType.cpp | 5 +- turbo-ext/src/TemplateArgumentConstraints.cpp | 2 +- turbo-ext/src/TemplateArgumentObserver.cpp | 2 +- turbo-ext/src/TemplateArgumentResolver.cpp | 2 +- turbo-ext/src/TypeTraits.cpp | 52 +- turbo-ext/src/TypeTraits.h | 10 +- turbo-ext/src/UnionType.cpp | 10 +- .../src/UnresolvedTemplateArgumentType.cpp | 3 +- turbo-ext/src/VoidType.cpp | 10 +- .../src/generated/AccessoryArrayListType.h | 40 +- .../AccessoryDecimalIntegerStringType.h | 40 +- .../generated/AccessoryLiteralStringType.h | 40 +- .../generated/AccessoryLowercaseStringType.h | 40 +- .../generated/AccessoryNonEmptyStringType.h | 40 +- .../generated/AccessoryNonFalsyStringType.h | 40 +- .../generated/AccessoryNumericStringType.h | 40 +- .../generated/AccessoryUppercaseStringType.h | 40 +- turbo-ext/src/generated/ArrayType.h | 1 + turbo-ext/src/generated/ArrayTypeTrait.h | 23 +- turbo-ext/src/generated/BooleanType.h | 36 +- turbo-ext/src/generated/CallableType.h | 36 +- .../src/generated/ClassConstantAccessType.h | 1 + turbo-ext/src/generated/ClosureType.h | 36 +- turbo-ext/src/generated/ConditionalType.h | 1 + .../generated/ConditionalTypeForParameter.h | 1 + turbo-ext/src/generated/ConstantArrayType.h | 1 + turbo-ext/src/generated/FloatType.h | 36 +- turbo-ext/src/generated/GetTemplateTypeType.h | 1 + turbo-ext/src/generated/HasMethodType.h | 40 +- turbo-ext/src/generated/HasOffsetType.h | 40 +- turbo-ext/src/generated/HasOffsetValueType.h | 40 +- turbo-ext/src/generated/HasPropertyType.h | 40 +- .../generated/InitializerExprTypeResolver.h | 275 +- turbo-ext/src/generated/IntegerRangeType.h | 31 +- turbo-ext/src/generated/IntegerType.h | 32 +- turbo-ext/src/generated/IntersectionType.h | 94 +- turbo-ext/src/generated/IterableType.h | 36 +- turbo-ext/src/generated/KeyOfType.h | 1 + .../generated/LateResolvableArrayShapeType.h | 1 + .../src/generated/LateResolvableTypeTrait.h | 35 +- turbo-ext/src/generated/MixedType.h | 36 +- turbo-ext/src/generated/NeverType.h | 36 +- turbo-ext/src/generated/NewObjectType.h | 1 + turbo-ext/src/generated/NonEmptyArrayType.h | 40 +- .../generated/NonexistentParentClassType.h | 36 +- turbo-ext/src/generated/NullType.h | 32 +- turbo-ext/src/generated/ObjectShapeType.h | 38 +- turbo-ext/src/generated/ObjectType.h | 32 +- .../src/generated/ObjectWithoutClassType.h | 38 +- turbo-ext/src/generated/OffsetAccessType.h | 1 + turbo-ext/src/generated/OperatorTypeHelper.h | 241 ++ turbo-ext/src/generated/OperatorTypeTrait.h | 104 + turbo-ext/src/generated/OversizedArrayType.h | 40 +- turbo-ext/src/generated/ResourceType.h | 36 +- turbo-ext/src/generated/StaticType.h | 36 +- turbo-ext/src/generated/StrictMixedType.h | 36 +- turbo-ext/src/generated/StringType.h | 32 +- turbo-ext/src/generated/UnionType.h | 76 +- .../UnresolvedTemplateArgumentType.h | 255 +- turbo-ext/src/generated/ValueOfType.h | 1 + turbo-ext/src/generated/VoidType.h | 36 +- turbo-ext/src/support.h | 33 + turbo-ext/tests/initializer-escape.php | 5 +- turbo-ext/tests/smoke.php | 6 +- turbo-ext/tests/type-family.php | 60 +- 145 files changed, 6895 insertions(+), 5559 deletions(-) create mode 100644 src/Type/OperatorTypeHelper.php create mode 100644 src/Type/Traits/OperatorTypeTrait.php create mode 100644 turbo-ext/src/IetrSupport.h create mode 100644 turbo-ext/src/OperatorTypeHelper.cpp create mode 100644 turbo-ext/src/generated/OperatorTypeHelper.h create mode 100644 turbo-ext/src/generated/OperatorTypeTrait.h diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fc79f566e9e..075d5f53b9a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -427,46 +427,10 @@ parameters: count: 1 path: src/Reflection/ClassReflection.php - - - rawMessage: Binary operation "&" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: Binary operation "*" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: Binary operation "+" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: Binary operation "-" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: Binary operation "^" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: Binary operation "|" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' identifier: phpstanApi.instanceofType - count: 18 + count: 2 path: src/Reflection/InitializerExprTypeResolver.php - @@ -481,30 +445,6 @@ parameters: count: 3 path: src/Reflection/InitializerExprTypeResolver.php - - - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' - identifier: phpstanApi.instanceofType - count: 6 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: PHPDoc tag @var with type float|int is not subtype of native type int. - identifier: varTag.nativeType - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: PHPDoc tag @var with type float|int is not subtype of type int. - identifier: varTag.type - count: 4 - path: src/Reflection/InitializerExprTypeResolver.php - - - - rawMessage: PHPDoc tag @var with type float|int|null is not subtype of type int|null. - identifier: varTag.type - count: 6 - path: src/Reflection/InitializerExprTypeResolver.php - - rawMessage: Creating new PHPStan\Php8StubsMap is not covered by backward compatibility promise. The class might change in a minor PHPStan version. identifier: phpstanApi.constructor @@ -1513,6 +1453,72 @@ parameters: count: 4 path: src/Type/ObjectWithoutClassType.php + - + rawMessage: Binary operation "&" between bool|float|int|string|null and bool|float|int|string|null results in an error. + identifier: binaryOp.invalid + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: Binary operation "*" between bool|float|int|string|null and bool|float|int|string|null results in an error. + identifier: binaryOp.invalid + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: Binary operation "+" between bool|float|int|string|null and bool|float|int|string|null results in an error. + identifier: binaryOp.invalid + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: Binary operation "-" between bool|float|int|string|null and bool|float|int|string|null results in an error. + identifier: binaryOp.invalid + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: Binary operation "^" between bool|float|int|string|null and bool|float|int|string|null results in an error. + identifier: binaryOp.invalid + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: Binary operation "|" between bool|float|int|string|null and bool|float|int|string|null results in an error. + identifier: binaryOp.invalid + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' + identifier: phpstanApi.instanceofType + count: 16 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' + identifier: phpstanApi.instanceofType + count: 6 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: PHPDoc tag @var with type float|int is not subtype of native type int. + identifier: varTag.nativeType + count: 1 + path: src/Type/OperatorTypeHelper.php + + - + rawMessage: PHPDoc tag @var with type float|int is not subtype of type int. + identifier: varTag.type + count: 4 + 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. identifier: phpstanApi.runtimeReflection diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 42451466f2e..d23878a5a8d 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection; use Closure; -use Nette\Utils\Strings; use PhpParser\Node\Arg; use PhpParser\Node\ComplexType; use PhpParser\Node\Expr; @@ -45,15 +44,8 @@ use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; -use PHPStan\Type\Accessory\AccessoryLowercaseStringType; -use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; -use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; -use PHPStan\Type\Accessory\AccessoryNumericStringType; -use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasOffsetValueType; -use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; -use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\BooleanType; use PHPStan\Type\CallableAssertionsHelper; use PHPStan\Type\ClassStringType; @@ -66,7 +58,6 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Constant\OversizedArrayBuilder; use PHPStan\Type\ConstantScalarType; -use PHPStan\Type\ConstantTypeHelper; use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; @@ -97,38 +88,22 @@ use PHPStan\Type\TypehintHelper; use PHPStan\Type\TypeResult; use PHPStan\Type\TypeTraverser; -use PHPStan\Type\TypeUtils; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnaryOperatorTypeSpecifyingExtensionRegistry; use PHPStan\Type\UnionType; use stdClass; use Throwable; -use function abs; use function array_key_exists; use function array_keys; use function array_map; use function array_merge; use function array_values; -use function assert; -use function ceil; use function count; use function dirname; -use function floor; use function in_array; -use function intval; -use function is_finite; -use function is_float; -use function is_int; -use function is_numeric; use function is_string; -use function max; -use function min; use function sprintf; -use function str_starts_with; use function strtolower; -use const INF; -use const PHP_INT_MAX; -use const PHP_INT_MIN; #[AutowiredService] #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/InitializerExprTypeResolver.cpp')] @@ -549,115 +524,7 @@ public function getConcatType(Expr $left, Expr $right, callable $getTypeCallback public function resolveConcatType(Type $left, Type $right): Type { - $leftStringType = $left->toString(); - $rightStringType = $right->toString(); - if (TypeCombinator::union( - $leftStringType, - $rightStringType, - ) instanceof ErrorType) { - return new ErrorType(); - } - - if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') { - return $rightStringType; - } - - if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') { - return $leftStringType; - } - - if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) { - return $leftStringType->append($rightStringType); - } - - $leftConstantStrings = $leftStringType->getConstantStrings(); - $rightConstantStrings = $rightStringType->getConstantStrings(); - $combinedConstantStringsCount = count($leftConstantStrings) * count($rightConstantStrings); - - // we limit the number of union-types for performance reasons - if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= self::CALCULATE_SCALARS_LIMIT) { - $strings = []; - - foreach ($leftConstantStrings as $leftConstantString) { - if ($leftConstantString->getValue() === '') { - $strings = array_merge($strings, $rightConstantStrings); - - continue; - } - - foreach ($rightConstantStrings as $rightConstantString) { - if ($rightConstantString->getValue() === '') { - $strings[] = $leftConstantString; - - continue; - } - - $strings[] = $leftConstantString->append($rightConstantString); - } - } - - if (count($strings) > 0) { - return TypeCombinator::union(...$strings); - } - } - - $accessoryTypes = []; - if ($leftStringType->isNonEmptyString()->and($rightStringType->isNonEmptyString())->yes()) { - $accessoryTypes[] = new AccessoryNonFalsyStringType(); - } elseif ($leftStringType->isNonFalsyString()->or($rightStringType->isNonFalsyString())->yes()) { - $accessoryTypes[] = new AccessoryNonFalsyStringType(); - } elseif ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) { - $accessoryTypes[] = new AccessoryNonEmptyStringType(); - } - - if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) { - $accessoryTypes[] = new AccessoryLiteralStringType(); - } - - if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) { - $accessoryTypes[] = new AccessoryLowercaseStringType(); - } - - if ($leftStringType->isUppercaseString()->and($rightStringType->isUppercaseString())->yes()) { - $accessoryTypes[] = new AccessoryUppercaseStringType(); - } - - $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); - if ($leftNumericStringNonEmpty->isNumericString()->yes()) { - $validationCallback = $left->isInteger()->yes() - ? static fn (string $value): bool => !str_starts_with($value, '-') - : static fn (string $value): bool => Strings::match($value, '#^\d+$#') !== null; - - $allRightConstantsZeroOrMore = false; - foreach ($rightConstantStrings as $rightConstantString) { - if ($rightConstantString->getValue() === '') { - continue; - } - - if ( - !is_numeric($rightConstantString->getValue()) - || !$validationCallback($rightConstantString->getValue()) - ) { - $allRightConstantsZeroOrMore = false; - break; - } - - $allRightConstantsZeroOrMore = true; - } - - $zeroOrMoreInteger = IntegerRangeType::fromInterval(0, null); - $nonNegativeRight = $allRightConstantsZeroOrMore || $zeroOrMoreInteger->isSuperTypeOf($right)->yes(); - if ($nonNegativeRight) { - $accessoryTypes[] = new AccessoryNumericStringType(); - } - } - - if (count($accessoryTypes) > 0) { - $accessoryTypes[] = new StringType(); - return new IntersectionType($accessoryTypes); - } - - return new StringType(); + return $left->stringConcatenate($right, $this->phpVersion); } /** @@ -1077,56 +944,9 @@ public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCall $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseAnd($left, $right), $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a & $b); - if ($result instanceof Type) { - return $result; - } - - // computed before optimizeScalarType() below widens integer ranges to int - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($result === self::IS_SCALAR_TYPE) { - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([new IntegerType(), new StringType()]); - } - - $leftIsString = $leftType->isString(); - $rightIsString = $rightType->isString(); - if ( - ($leftIsString->yes() || $leftType instanceof MixedType) - && ($rightIsString->yes() || $rightType instanceof MixedType) - ) { - return new StringType(); - } - if ($leftIsString->maybe() && $rightIsString->maybe()) { - return new ErrorType(); - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $bitwiseAndRange = $this->computeBitwiseAndRange($leftNumberType, $rightNumberType); - if ($bitwiseAndRange !== null) { - return $bitwiseAndRange; - } - - return new IntegerType(); + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseAnd($left, $right), $leftType, $rightType) + ?? $leftType->bitwiseAnd($rightType, $this->phpVersion); } /** @@ -1137,56 +957,9 @@ public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallb $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseOr($left, $right), $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a | $b); - if ($result instanceof Type) { - return $result; - } - - // computed before optimizeScalarType() below widens integer ranges to int - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($result === self::IS_SCALAR_TYPE) { - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([new IntegerType(), new StringType()]); - } - - $leftIsString = $leftType->isString(); - $rightIsString = $rightType->isString(); - if ( - ($leftIsString->yes() || $leftType instanceof MixedType) - && ($rightIsString->yes() || $rightType instanceof MixedType) - ) { - return new StringType(); - } - if ($leftIsString->maybe() && $rightIsString->maybe()) { - return new ErrorType(); - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $bitwiseOrRange = $this->computeBitwiseOrXorRange($leftNumberType, $rightNumberType); - if ($bitwiseOrRange !== null) { - return $bitwiseOrRange; - } - - return new IntegerType(); + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseOr($left, $right), $leftType, $rightType) + ?? $leftType->bitwiseOr($rightType, $this->phpVersion); } /** @@ -1197,116 +970,9 @@ public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCall $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseXor($left, $right), $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $result = $this->getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a ^ $b); - if ($result instanceof Type) { - return $result; - } - - // computed before optimizeScalarType() below widens integer ranges to int - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($result === self::IS_SCALAR_TYPE) { - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([new IntegerType(), new StringType()]); - } - - $leftIsString = $leftType->isString(); - $rightIsString = $rightType->isString(); - if ( - ($leftIsString->yes() || $leftType instanceof MixedType) - && ($rightIsString->yes() || $rightType instanceof MixedType) - ) { - return new StringType(); - } - if ($leftIsString->maybe() && $rightIsString->maybe()) { - return new ErrorType(); - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $bitwiseXorRange = $this->computeBitwiseOrXorRange($leftNumberType, $rightNumberType); - if ($bitwiseXorRange !== null) { - return $bitwiseXorRange; - } - - return new IntegerType(); - } - - private const IS_SCALAR_TYPE = 1; - private const IS_UNKNOWN = 2; - - /** - * @param callable(bool|float|int|string|null, bool|float|int|string|null):string $operationCallable - * - * @return self::IS_UNKNOWN|self::IS_SCALAR_TYPE|Type - */ - private function getFiniteOrConstantScalarTypes(Type $leftType, Type $rightType, callable $operationCallable): int|Type - { - if ($leftType instanceof IntegerRangeType) { - $leftTypes = $leftType->getFiniteTypes(); - } else { - $leftTypes = $leftType->getConstantScalarTypes(); - } - if ($rightType instanceof IntegerRangeType) { - $rightTypes = $rightType->getFiniteTypes(); - } else { - $rightTypes = $rightType->getConstantScalarTypes(); - } - - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - - if ($leftTypesCount === 0 || $rightTypesCount === 0) { - return self::IS_UNKNOWN; - } - - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if ($generalize) { - return self::IS_SCALAR_TYPE; - } - - $resultTypes = []; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { - $resultValue = $operationCallable($leftTypeInner->getValue(), $rightTypeInner->getValue()); - $resultType = $this->getTypeFromValue($resultValue); - } else { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $resultValue = $operationCallable($leftNumberType->getValue(), $rightNumberType->getValue()); - $resultType = $this->getTypeFromValue($resultValue); - } - $resultTypes[] = $resultType; - } - } - return TypeCombinator::union(...$resultTypes); + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\BitwiseXor($left, $right), $leftType, $rightType) + ?? $leftType->bitwiseXor($rightType, $this->phpVersion); } /** @@ -1348,8 +1014,15 @@ public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - $result = $this->getDivTypeFromTypes($left, $right, $leftType, $rightType); + $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Div($left, $right), $leftType, $rightType); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + + $result = $leftType->arithmeticDivide($rightType, $this->phpVersion); + // the remainder can be narrowed in the scope, e.g. by `$a % $b === 0` if ($leftType->isInteger()->yes() && $rightType->isInteger()->yes()) { $modType = $getTypeCallback(new BinaryOp\Mod($left, $right)); if ($modType->isInteger()->yes() && (new ConstantIntegerType(0))->isSuperTypeOf($modType)->yes()) { @@ -1365,831 +1038,96 @@ public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): return $result; } - private function getDivTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type + /** + * @param callable(Expr): Type $getTypeCallback + */ + public function getModType(Expr $left, Expr $right, callable $getTypeCallback): Type { - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - if (in_array($rightNumberType->getValue(), [0, 0.0], true)) { - return new ErrorType(); - } - - $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid - $resultTypes[] = $resultType; - } - } - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - $rightScalarValues = $rightType->toNumber()->getConstantScalarValues(); - foreach ($rightScalarValues as $scalarValue) { - if (in_array($scalarValue, [0, 0.0], true)) { - return new ErrorType(); - } - } + $leftType = $getTypeCallback($left); + $rightType = $getTypeCallback($right); - return $this->resolveCommonMath(new BinaryOp\Div($left, $right), $leftType, $rightType); + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mod($left, $right), $leftType, $rightType) + ?? $leftType->arithmeticModulo($rightType, $this->phpVersion); } /** * @param callable(Expr): Type $getTypeCallback */ - public function getModType(Expr $left, Expr $right, callable $getTypeCallback): Type + public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): Type { $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $extensionSpecified = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mod($left, $right), $leftType, $rightType); - if ($extensionSpecified !== null) { - return $extensionSpecified; - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $rightIntegerValue = (int) $rightNumberType->getValue(); - if ($rightIntegerValue === 0) { - return new ErrorType(); - } - - $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue); - $resultTypes[] = $resultType; - } - } - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - $integerType = $rightType->toInteger(); - if ($integerType instanceof ConstantIntegerType && $integerType->getValue() === 1) { - return new ConstantIntegerType(0); - } - - $rightScalarValues = $rightType->toNumber()->getConstantScalarValues(); - foreach ($rightScalarValues as $scalarValue) { - - if (in_array($scalarValue, [0, 0.0], true)) { - return new ErrorType(); - } - } - - $maxMagnitude = null; - $divisorBounds = $this->getIntegerBounds($rightType->toInteger()); - if ($divisorBounds !== null) { - $maxMagnitude = self::getMaxModuloMagnitude($divisorBounds[0], $divisorBounds[1]); - } - - [$leftMin, $leftMax] = $this->getIntegerBounds($leftType->toInteger()) ?? [null, null]; - if ($leftMin === null && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()) { - $leftMin = 0; - } - if ($leftMax === null && IntegerRangeType::fromInterval(null, 0)->isSuperTypeOf($leftType)->yes()) { - $leftMax = 0; - } - - // The result has the sign of the dividend and is never bigger in magnitude than either - // the dividend or the largest magnitude the divisor can have. - if ($leftMax !== null && $leftMax <= 0) { - $rangeMax = 0; - } elseif ($maxMagnitude === null) { - $rangeMax = $leftMax; - } elseif ($leftMax === null) { - $rangeMax = $maxMagnitude; - } else { - $rangeMax = min($leftMax, $maxMagnitude); - } + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Plus($left, $right), $leftType, $rightType) + ?? $leftType->arithmeticAdd($rightType, $this->phpVersion); + } - if ($leftMin !== null && $leftMin >= 0) { - $rangeMin = 0; - } elseif ($maxMagnitude === null) { - $rangeMin = $leftMin; - } elseif ($leftMin === null) { - $rangeMin = -$maxMagnitude; - } else { - $rangeMin = max($leftMin, -$maxMagnitude); - } + /** + * @param callable(Expr): Type $getTypeCallback + */ + public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback): Type + { + $leftType = $getTypeCallback($left); + $rightType = $getTypeCallback($right); - return IntegerRangeType::fromInterval($rangeMin, $rangeMax); + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Minus($left, $right), $leftType, $rightType) + ?? $leftType->arithmeticSubtract($rightType, $this->phpVersion); } /** - * Highest possible absolute value of `$x % $divisor`, which is 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 as unbounded too: `abs(PHP_INT_MIN)` does not - * fit into an integer, and the bound it stands for is PHP_INT_MAX, which is all an unbounded - * result can hold anyway. + * @param callable(Expr): Type $getTypeCallback */ - private static function getMaxModuloMagnitude(?int $divisorMin, ?int $divisorMax): ?int + public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): Type { - if ($divisorMin === null || $divisorMax === null || $divisorMin === PHP_INT_MIN) { - return null; - } - - $magnitude = max(abs($divisorMin), abs($divisorMax)); - if ($magnitude === 0) { - return null; - } + $leftType = $getTypeCallback($left); + $rightType = $getTypeCallback($right); - return $magnitude - 1; + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mul($left, $right), $leftType, $rightType) + ?? $leftType->arithmeticMultiply($rightType, $this->phpVersion); } /** - * @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. + * @param callable(Expr): Type $getTypeCallback */ - private function getIntegerBounds(Type $type): ?array + public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): Type { - $innerTypes = $type instanceof UnionType ? $type->getTypes() : [$type]; - if ($innerTypes === []) { - return null; - } - - $min = null; - $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; - } + $leftType = $getTypeCallback($left); + $rightType = $getTypeCallback($right); - if ($innerMin === null) { - $unboundedMin = true; - } elseif ($min === null || $innerMin < $min) { - $min = $innerMin; - } + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType) + ?? $leftType->arithmeticPower($rightType, $this->phpVersion); + } - if ($innerMax === null) { - $unboundedMax = true; - } elseif ($max === null || $innerMax > $max) { - $max = $innerMax; - } - } + /** + * @param callable(Expr): Type $getTypeCallback + */ + public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallback): Type + { + $leftType = $getTypeCallback($left); + $rightType = $getTypeCallback($right); - return [ - $unboundedMin ? null : $min, - $unboundedMax ? null : $max, - ]; + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftLeft($left, $right), $leftType, $rightType) + ?? $leftType->bitwiseShiftLeft($rightType, $this->phpVersion); } /** * @param callable(Expr): Type $getTypeCallback */ - public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): Type + public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCallback): Type { $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue()); - $resultTypes[] = $resultType; - } - } - - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - $leftConstantArrays = $leftType->getConstantArrays(); - $rightConstantArrays = $rightType->getConstantArrays(); - - $leftCount = count($leftConstantArrays); - $rightCount = count($rightConstantArrays); - if ($leftCount > 0 && $rightCount > 0 - && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) { - $resultTypes = []; - foreach ($rightConstantArrays as $rightConstantArray) { - foreach ($leftConstantArrays as $leftConstantArray) { - $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); - foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) { - $optional = $leftConstantArray->isOptionalKey($i); - $valueType = $leftConstantArray->getOffsetValueType($leftKeyType); - if (!$optional) { - if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) { - $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType)); - } - } - $newArrayBuilder->setOffsetValueType( - $leftKeyType, - $valueType, - $optional, - ); - } - $resultTypes[] = $newArrayBuilder->getArray(); - } - } - return TypeCombinator::union(...$resultTypes); - } - - // `array{...} + array` keeps the known keyed prefix and - // folds the right-hand side into an open `...` tail. This - // only applies when the left arrays' sealedness is known (bleeding edge); - // otherwise the legacy behavior below is kept for backward compatibility. - if ($leftCount > 0 && $rightCount === 0 - && $leftCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT - && $rightType->isArray()->yes()) { - $allSealednessKnown = true; - foreach ($leftConstantArrays as $leftConstantArray) { - if ($leftConstantArray->isUnsealed()->maybe()) { - $allSealednessKnown = false; - break; - } - } - - if ($allSealednessKnown) { - $rightKeyType = $rightType->getIterableKeyType(); - $rightValueType = $rightType->getIterableValueType(); - $resultTypes = []; - foreach ($leftConstantArrays as $leftConstantArray) { - $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($leftConstantArray); - $existingUnsealed = $leftConstantArray->getUnsealedTypes(); - if ($leftConstantArray->isUnsealed()->yes() && $existingUnsealed !== null) { - $newArrayBuilder->makeUnsealed( - TypeCombinator::union($existingUnsealed[0], $rightKeyType), - TypeCombinator::union($existingUnsealed[1], $rightValueType), - ); - } else { - $newArrayBuilder->makeUnsealed($rightKeyType, $rightValueType); - } - $resultTypes[] = $newArrayBuilder->getArray(); - } - return TypeCombinator::union(...$resultTypes); - } - } - - $leftIsArray = $leftType->isArray(); - $rightIsArray = $rightType->isArray(); - if ($leftIsArray->yes() && $rightIsArray->yes()) { - if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { - // to preserve BenevolentUnionType - $keyType = $leftType->getIterableKeyType(); - } else { - $keyTypes = []; - foreach ([ - $leftType->getIterableKeyType(), - $rightType->getIterableKeyType(), - ] as $keyType) { - $keyTypes[] = $keyType; - } - $keyType = TypeCombinator::union(...$keyTypes); - } - - $leftIterableValueType = $leftType->getIterableValueType(); - $arrayType = new ArrayType( - $keyType, - TypeCombinator::union($leftIterableValueType, $rightType->getIterableValueType()), - ); - - $accessories = []; - if ($leftCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - $constantArray = $leftConstantArrays[0]; - foreach ($constantArray->getKeyTypes() as $offsetType) { - if (!$leftType->hasOffsetValueType($offsetType)->yes()) { - continue; - } - - $valueType = $leftType->getOffsetValueType($offsetType); - $accessories[] = new HasOffsetValueType($offsetType, $valueType); - } - } - - if ($rightCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - $constantArray = $rightConstantArrays[0]; - foreach ($constantArray->getKeyTypes() as $offsetType) { - if (!$rightType->hasOffsetValueType($offsetType)->yes()) { - continue; - } - - $valueType = TypeCombinator::union($leftIterableValueType, $rightType->getOffsetValueType($offsetType)); - $accessories[] = new HasOffsetValueType($offsetType, $valueType); - } - } - - if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { - $accessories[] = new NonEmptyArrayType(); - } - if ($leftType->isList()->yes() && $rightType->isList()->yes()) { - $accessories[] = new AccessoryArrayListType(); - } - - if (count($accessories) > 0) { - $arrayType = TypeCombinator::intersect($arrayType, ...$accessories); - } - - return $arrayType; - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - if ($leftIsArray->no() && $rightIsArray->no()) { - return new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - ]); - } - return new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - new ArrayType(new MixedType(), new MixedType()), - ]); - } - - if ( - ($leftIsArray->yes() && $rightIsArray->no()) - || ($leftIsArray->no() && $rightIsArray->yes()) - ) { - return new ErrorType(); - } - - if ( - ($leftIsArray->yes() && $rightIsArray->maybe()) - || ($leftIsArray->maybe() && $rightIsArray->yes()) - ) { - $resultType = new ArrayType(new MixedType(), new MixedType()); - if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { - return TypeCombinator::intersect($resultType, new NonEmptyArrayType()); - } - - return $resultType; - } - - if ($leftIsArray->maybe() && $rightIsArray->maybe()) { - $plusable = new UnionType([ - new StringType(), - new FloatType(), - new IntegerType(), - new ArrayType(new MixedType(), new MixedType()), - new BooleanType(), - ]); - - $plusableSuperTypeOfLeft = $plusable->isSuperTypeOf($leftType)->yes(); - $plusableSuperTypeOfRight = $plusable->isSuperTypeOf($rightType)->yes(); - if ($plusableSuperTypeOfLeft && $plusableSuperTypeOfRight) { - return TypeCombinator::union($leftType, $rightType); - } - if ($plusableSuperTypeOfLeft && $rightType instanceof MixedType) { - return $leftType; - } - if ($plusableSuperTypeOfRight && $leftType instanceof MixedType) { - return $rightType; - } - } - - return $this->resolveCommonMath(new BinaryOp\Plus($left, $right), $leftType, $rightType); - } - - /** - * @param callable(Expr): Type $getTypeCallback - */ - public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback): Type - { - $leftType = $getTypeCallback($left); - $rightType = $getTypeCallback($right); - - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue()); - $resultTypes[] = $resultType; - } - } - - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType); - } - - /** - * @param callable(Expr): Type $getTypeCallback - */ - public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): Type - { - $leftType = $getTypeCallback($left); - $rightType = $getTypeCallback($right); - - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue()); - $resultTypes[] = $resultType; - } - } - - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - $leftNumberType = $leftType->toNumber(); - if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) { - if ($rightType->isFloat()->yes()) { - return new ConstantFloatType(0.0); - } - return new ConstantIntegerType(0); - } - $rightNumberType = $rightType->toNumber(); - if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) { - if ($leftType->isFloat()->yes()) { - return new ConstantFloatType(0.0); - } - return new ConstantIntegerType(0); - } - - return $this->resolveCommonMath(new BinaryOp\Mul($left, $right), $leftType, $rightType); - } - - /** - * @param callable(Expr): Type $getTypeCallback - */ - public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): Type - { - $leftType = $getTypeCallback($left); - $rightType = $getTypeCallback($right); - - $extensionSpecified = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType); - if ($extensionSpecified !== null) { - return $extensionSpecified; - } - - $exponentiatedTyped = $leftType->exponentiate($rightType); - if (!$exponentiatedTyped instanceof ErrorType) { - return $exponentiatedTyped; - } - - return new ErrorType(); - } - - /** - * @param callable(Expr): Type $getTypeCallback - */ - public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallback): Type - { - $leftType = $getTypeCallback($left); - $rightType = $getTypeCallback($right); - - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftLeft($left, $right), $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - if ($rightNumberType->getValue() < 0) { - return new ErrorType(); - } - - $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue())); - $resultTypes[] = $resultType; - } - } - - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - return $this->resolveCommonMath(new Expr\BinaryOp\ShiftLeft($left, $right), $leftType, $rightType); - } - - /** - * @param callable(Expr): Type $getTypeCallback - */ - public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCallback): Type - { - $leftType = $getTypeCallback($left); - $rightType = $getTypeCallback($right); - - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftRight($left, $right), $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return $this->getNeverType($leftType, $rightType); - } - - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - if ($rightNumberType->getValue() < 0) { - return new ErrorType(); - } - - $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue())); - $resultTypes[] = $resultType; - } - } - - return TypeCombinator::union(...$resultTypes); - } - - $leftType = $this->optimizeScalarType($leftType); - $rightType = $this->optimizeScalarType($rightType); - } - - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - return $this->resolveCommonMath(new Expr\BinaryOp\ShiftRight($left, $right), $leftType, $rightType); - } - - private function optimizeScalarType(Type $type): Type - { - $types = []; - if ($type->isInteger()->yes()) { - $types[] = new IntegerType(); - } - if ($type->isString()->yes()) { - $types[] = new StringType(); - } - if ($type->isFloat()->yes()) { - $types[] = new FloatType(); - } - if ($type->isNull()->yes()) { - $types[] = new NullType(); - } - - if (count($types) === 0) { - return new ErrorType(); - } - - if (count($types) === 1) { - return $types[0]; - } - - return new UnionType($types); - } - - /** - * @return array{int, int|null}|null [min, max] of a type known to be a non-negative integer, - * with a null max when it has no finite upper bound. Null when the type is not known - * to be a non-negative integer. - */ - private function getNonNegativeIntegerBounds(Type $type): ?array - { - if ($type instanceof IntegerRangeType) { - $min = $type->getMin(); - if ($min !== null && $min >= 0) { - return [$min, $type->getMax()]; - } - return null; - } - if ($type instanceof ConstantIntegerType && $type->getValue() >= 0) { - return [$type->getValue(), $type->getValue()]; - } - return null; - } - - /** - * Expects the operands already converted with toNumber(). - * - * A single non-negative operand is enough to bound the result: the result's bits - * are a subset of that operand's bits, so it stays within int<0, thatMax>. - * With no finite max on any non-negative side the result is still int<0, max>. - */ - private function computeBitwiseAndRange(Type $leftNumberType, Type $rightNumberType): ?Type - { - $leftBounds = $this->getNonNegativeIntegerBounds($leftNumberType); - $rightBounds = $this->getNonNegativeIntegerBounds($rightNumberType); - if ($leftBounds === null && $rightBounds === null) { - return null; - } - - $maxValues = []; - if ($leftBounds !== null && $leftBounds[1] !== null) { - $maxValues[] = $leftBounds[1]; - } - if ($rightBounds !== null && $rightBounds[1] !== null) { - $maxValues[] = $rightBounds[1]; - } - - return IntegerRangeType::fromInterval(0, $maxValues === [] ? null : min($maxValues)); - } - - /** - * Expects the operands already converted with toNumber(). - * - * Both operands have to be non-negative: they keep the sign bit of the result clear. - * Without a finite max on either side the result is still int<0, max>. - */ - private function computeBitwiseOrXorRange(Type $leftNumberType, Type $rightNumberType): ?Type - { - $leftBounds = $this->getNonNegativeIntegerBounds($leftNumberType); - $rightBounds = $this->getNonNegativeIntegerBounds($rightNumberType); - if ($leftBounds === null || $rightBounds === null) { - return null; - } - if ($leftBounds[1] === null || $rightBounds[1] === null) { - return IntegerRangeType::fromInterval(0, null); - } - - return IntegerRangeType::fromInterval(0, self::allBitsMask(max($leftBounds[1], $rightBounds[1]))); - } - - /** - * Propagates the highest set bit of a non-negative value into all lower bits: - * 200 becomes 255, 10 becomes 15. - */ - private static function allBitsMask(int $value): int - { - $value |= $value >> 1; - $value |= $value >> 2; - $value |= $value >> 4; - $value |= $value >> 8; - $value |= $value >> 16; - $value |= $value >> 32; - - return $value; - } + return $this->operatorTypeSpecifyingExtensionRegistry + ->callOperatorTypeSpecifyingExtensions(new BinaryOp\ShiftRight($left, $right), $leftType, $rightType) + ?? $leftType->bitwiseShiftRight($rightType, $this->phpVersion); + } /** * @return TypeResult @@ -2321,367 +1259,6 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, return new TypeResult($resultType->toBoolean(), []); } - /** - * @param BinaryOp\Plus|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Div|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $expr - */ - private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $rightType): Type - { - $specifiedTypes = $this->operatorTypeSpecifyingExtensionRegistry - ->callOperatorTypeSpecifyingExtensions($expr, $leftType, $rightType); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - $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 $this->integerRangeMath( - $leftNumberType, - $expr, - $rightNumberType, - ); - } elseif ($leftNumberType instanceof UnionType) { - $unionParts = []; - - foreach ($leftNumberType->getTypes() as $type) { - $numberType = $type->toNumber(); - if ($numberType instanceof IntegerRangeType || $numberType instanceof ConstantIntegerType) { - $unionParts[] = $this->integerRangeMath($numberType, $expr, $rightNumberType); - } else { - $unionParts[] = $numberType; - } - } - - $union = TypeCombinator::union(...$unionParts); - if ($leftNumberType instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($union)->toNumber(); - } - - return $union->toNumber(); - } - } - - if ( - $leftType->isArray()->yes() - || $rightType->isArray()->yes() - || $types->isArray()->yes() - ) { - return new ErrorType(); - } - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) { - return $this->getNeverType($leftNumberType, $rightNumberType); - } - - if ( - $leftNumberType->isFloat()->yes() - || $rightNumberType->isFloat()->yes() - ) { - if ($expr instanceof Expr\BinaryOp\ShiftLeft || $expr instanceof Expr\BinaryOp\ShiftRight) { - return new IntegerType(); - } - return new FloatType(); - } - - $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); - if ($expr instanceof Expr\BinaryOp\Div) { - if ($types instanceof MixedType || $resultType->isInteger()->yes()) { - return new BenevolentUnionType([new IntegerType(), new FloatType()]); - } - - return new UnionType([new IntegerType(), new FloatType()]); - } - - if ($types instanceof MixedType - || $leftType instanceof BenevolentUnionType - || $rightType instanceof BenevolentUnionType - ) { - return TypeUtils::toBenevolentUnion($resultType); - } - - return $resultType; - } - - /** - * @param ConstantIntegerType|IntegerRangeType $range - * @param BinaryOp\Div|BinaryOp\Minus|BinaryOp\Mul|BinaryOp\Plus|BinaryOp\ShiftLeft|BinaryOp\ShiftRight $node - */ - private function integerRangeMath(Type $range, BinaryOp $node, 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[] = $this->integerRangeMath($range, $node, $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 ($node instanceof BinaryOp\Plus) { - 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 ($node instanceof BinaryOp\Minus) { - 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 { - if ($rangeMin === $rangeMax && $rangeMin !== null - && ($operand->getMin() === null || $operand->getMax() === null)) { - $min = null; - $max = $rangeMin; - } else { - if ($operand->getMin() === null) { - $min = null; - } elseif ($rangeMin !== null) { - if ($operand->getMax() !== null) { - /** @var int|float $min */ - $min = $rangeMin - $operand->getMax(); - } else { - /** @var int|float $min */ - $min = $rangeMin - $operand->getMin(); - } - } else { - $min = null; - } - - if ($operand->getMax() === null) { - $min = null; - $max = null; - } elseif ($rangeMax !== null) { - if ($rangeMin !== null && $operand->getMin() === null) { - /** @var int|float $min */ - $min = $rangeMin - $operand->getMax(); - $max = null; - } elseif ($operand->getMin() !== null) { - /** @var int|float $max */ - $max = $rangeMax - $operand->getMin(); - } else { - $max = null; - } - } else { - $max = null; - } - - if ($min !== null && $max !== null && $min > $max) { - [$min, $max] = [$max, $min]; - } - } - } - } elseif ($node instanceof Expr\BinaryOp\Mul) { - $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 ($node instanceof Expr\BinaryOp\Div) { - 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( - $this->integerRangeMath($range, $node, $negativeOperand), - $this->integerRangeMath($range, $node, $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( - $this->integerRangeMath($negativeRange, $node, $operand), - $this->integerRangeMath($positiveRange, $node, $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 ($node instanceof Expr\BinaryOp\ShiftLeft) { - 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 ($node instanceof Expr\BinaryOp\ShiftRight) { - 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; - } - /** * With $nativeTypesPromoted, PHPDoc types of the constants are ignored: * a constant that can be overridden in a child class resolves only to its native type. @@ -2901,46 +1478,9 @@ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type { $type = $getTypeCallback($expr); - $specifiedTypes = $this->unaryOperatorTypeSpecifyingExtensionRegistry - ->callUnaryOperatorTypeSpecifyingExtensions('-', $type); - if ($specifiedTypes !== null) { - return $specifiedTypes; - } - - $type = $this->getUnaryMinusTypeFromType($expr, $type); - if ($type instanceof IntegerRangeType) { - return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); - } - - return $type; - } - - public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type - { - $type = $type->toNumber(); - $scalarValues = $type->getConstantScalarValues(); - - if (count($scalarValues) > 0) { - $newTypes = []; - foreach ($scalarValues as $scalarValue) { - if (is_int($scalarValue)) { - /** @var int|float $newValue */ - $newValue = -$scalarValue; - if (!is_int($newValue)) { - // Negating the smallest integer overflows into a float. - $newTypes[] = new ConstantFloatType($newValue); - continue; - } - $newTypes[] = new ConstantIntegerType($newValue); - } elseif (is_float($scalarValue)) { - $newTypes[] = new ConstantFloatType(-$scalarValue); - } - } - - return TypeCombinator::union(...$newTypes); - } - - return $type; + return $this->unaryOperatorTypeSpecifyingExtensionRegistry + ->callUnaryOperatorTypeSpecifyingExtensions('-', $type) + ?? $type->arithmeticNegate($this->phpVersion); } /** @@ -3017,37 +1557,6 @@ private function resolveTypeByNameWithLateStaticBinding(Name $class, Type $class return $classType; } - /** - * 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; - } - - /** - * @param mixed $value - */ - private function getTypeFromValue($value): Type - { - return ConstantTypeHelper::getTypeFromValue($value); - } - private function getReflectionProvider(): ReflectionProvider { return $this->reflectionProviderProvider->getReflectionProvider(); diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index ccde7e7cab6..cca0437a806 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -26,6 +26,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -45,6 +46,7 @@ class AccessoryArrayListType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -561,7 +563,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index 2ea08328093..1483d1246d9 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -59,6 +60,7 @@ class AccessoryDecimalIntegerStringType implements CompoundType, AccessoryType use NonIterableTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct(private bool $inverse = false) @@ -459,7 +461,7 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 128fbcdd51d..d970d999f96 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -49,6 +50,7 @@ class AccessoryLiteralStringType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; use NonRemoveableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -380,7 +382,7 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 4f2eb475c38..70ac7182c39 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -49,6 +50,7 @@ class AccessoryLowercaseStringType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; use NonRemoveableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -385,7 +387,7 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 40d848ca05f..6ffdffcd707 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -50,6 +51,7 @@ class AccessoryNonEmptyStringType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; use UndecidedBooleanTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -389,7 +391,7 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index fc0d10e6eae..ebafce2bab7 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -33,6 +33,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -53,6 +54,7 @@ class AccessoryNonFalsyStringType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; use NonRemoveableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -386,7 +388,7 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 92b180b2718..e114df91396 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -50,6 +51,7 @@ class AccessoryNumericStringType implements CompoundType, AccessoryType use UndecidedBooleanTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -398,7 +400,7 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index ebc45570e84..d126956f2f4 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -49,6 +50,7 @@ class AccessoryUppercaseStringType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; use NonRemoveableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -385,7 +387,7 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 4c92ac1a5e3..ffae597200d 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -61,6 +62,7 @@ class HasMethodType implements AccessoryType, CompoundType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct(private string $methodName) @@ -326,7 +328,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 50606d8f1a1..0c14c997cb8 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -33,6 +33,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -60,6 +61,7 @@ class HasOffsetType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** * @api @@ -453,7 +455,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index da2e0570d94..fae6fea5737 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Traits\MaybeStringTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -61,6 +62,7 @@ class HasOffsetValueType implements CompoundType, AccessoryType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; public function __construct(private ConstantStringType|ConstantIntegerType $offsetType, private Type $valueType) { @@ -555,7 +557,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return new self($this->offsetType, $newValueType); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index 7a21158fa20..e1a60329030 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -28,6 +28,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -52,6 +53,7 @@ class HasPropertyType implements AccessoryType, CompoundType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct(private string $propertyName) @@ -302,7 +304,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 7f105918f78..cdf6d9e7b0f 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -27,6 +27,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; @@ -45,6 +46,7 @@ class NonEmptyArrayType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -540,7 +542,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 10f6d5b552b..115798dfc62 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -26,6 +26,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\UnionType; @@ -42,6 +43,7 @@ class OversizedArrayType implements CompoundType, AccessoryType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; public function __construct() { @@ -510,7 +512,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 974b89640e3..7a07b9d6422 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -20,6 +20,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -39,6 +40,7 @@ class BooleanType implements Type use NonGenericTypeTrait; use NonOffsetAccessibleTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -180,7 +182,7 @@ public function getFiniteTypes(): array ]; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return ExponentiateHelper::exponentiate($this, $exponent); } diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index b58dd0fb886..6bb9f55b60e 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -40,6 +40,7 @@ use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use function array_key_exists; @@ -61,6 +62,7 @@ class CallableType implements CompoundType, CallableParametersAcceptor use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @var list */ private array $parameters; @@ -800,7 +802,7 @@ public function isCommonCallable(): bool return $this->isCommonCallable; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 2bf0c3b332b..f3b22919c75 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -51,6 +51,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use function array_key_exists; use function array_map; @@ -68,6 +69,7 @@ class ClosureType implements TypeWithClassName, CallableParametersAcceptor use NonOffsetAccessibleTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @var list */ private array $parameters; @@ -982,7 +984,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index b3b5bcbee22..280a910a8ef 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -20,6 +20,7 @@ use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use function get_class; @@ -40,6 +41,7 @@ class FloatType implements Type use NonOffsetAccessibleTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -295,7 +297,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return ExponentiateHelper::exponentiate($this, $exponent); } diff --git a/src/Type/Generic/UnresolvedTemplateArgumentType.php b/src/Type/Generic/UnresolvedTemplateArgumentType.php index ff3d0180cae..b69c0b3a2ac 100644 --- a/src/Type/Generic/UnresolvedTemplateArgumentType.php +++ b/src/Type/Generic/UnresolvedTemplateArgumentType.php @@ -23,6 +23,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\NativeTypePosition; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; @@ -54,6 +55,8 @@ final class UnresolvedTemplateArgumentType implements CompoundType { + use OperatorTypeTrait; + public function __construct( private Expr $site, private TemplateType $templateType, @@ -658,9 +661,9 @@ public function getFiniteTypes(): array return $this->getDelegate()->getFiniteTypes(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return $this->getDelegate()->exponentiate($exponent); + return $this->getDelegate()->arithmeticPower($exponent, $phpVersion); } public function isCallable(): TrinaryLogic diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 4dd6249a6c1..fc3106380e4 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -15,6 +15,7 @@ use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Traits\OperatorTypeTrait; use function array_filter; use function array_map; use function assert; @@ -35,6 +36,8 @@ class IntegerRangeType extends IntegerType implements CompoundType { + use OperatorTypeTrait; + private function __construct(private ?int $min, private ?int $max) { parent::__construct(); @@ -673,12 +676,12 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { if ($exponent instanceof UnionType) { $results = []; foreach ($exponent->getTypes() as $unionType) { - $results[] = $this->exponentiate($unionType); + $results[] = $this->arithmeticPower($unionType, $phpVersion); } return TypeCombinator::union(...$results); } @@ -716,7 +719,7 @@ public function exponentiate(Type $exponent): Type } } - return parent::exponentiate($exponent); + return parent::arithmeticPower($exponent, $phpVersion); } /** diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 953d7c29b2b..1264cb42500 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -19,6 +19,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -38,6 +39,7 @@ class IntegerType implements Type use NonGenericTypeTrait; use NonOffsetAccessibleTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -194,7 +196,7 @@ public function getFiniteTypes(): array return []; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return ExponentiateHelper::exponentiate($this, $exponent); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 8052cdde3c2..e158ce989d5 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -54,6 +54,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use function array_filter; use function array_intersect_key; use function array_key_exists; @@ -83,6 +84,7 @@ class IntersectionType implements CompoundType use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** * Sorting must not reorder $types: describe() sorts for readability, but $types is the @@ -1772,9 +1774,9 @@ public function tryRemove(Type $typeToRemove): ?Type return $this->intersectTypes(static fn (Type $type): Type => TypeCombinator::remove($type, $typeToRemove)); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->exponentiate($exponent)); + return $this->intersectTypes(static fn (Type $type): Type => $type->arithmeticPower($exponent, $phpVersion)); } public function getFiniteTypes(): array diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index aee7aeb5224..1f2b484b25a 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -19,6 +19,7 @@ use PHPStan\Type\Traits\MaybeObjectTypeTrait; use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use Traversable; @@ -39,6 +40,7 @@ class IterableType implements CompoundType use UndecidedBooleanTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct( @@ -517,7 +519,7 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 4fb45184fb7..8186bf4f6a6 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -41,6 +41,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\SubstractableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use function get_class; @@ -55,6 +56,7 @@ class MixedType implements CompoundType, SubtractableType use UndecidedComparisonCompoundTypeTrait; use NonGeneralizableTypeTrait; use SubstractableTypeTrait; + use OperatorTypeTrait; private ?Type $subtractedType; @@ -1221,7 +1223,7 @@ public function tryRemove(Type $typeToRemove): ?Type return $this->subtract($typeToRemove); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new BenevolentUnionType([ new FloatType(), diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 7d57aa389e3..5d484908cb3 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -21,6 +21,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; @@ -34,6 +35,7 @@ class NeverType implements CompoundType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct(private bool $isExplicit = false, private ?string $reason = null) @@ -648,7 +650,7 @@ public function getEnumCaseObject(): ?EnumCaseObjectType return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return $this; } diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index d5624b25858..9708b9d72bf 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -25,6 +25,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -42,6 +43,7 @@ class NonexistentParentClassType implements Type use UndecidedComparisonTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; public function describe(VerbosityLevel $level): string { @@ -260,7 +262,7 @@ public function getEnumCaseObject(): ?EnumCaseObjectType return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index e8d6848fac9..7e3331691fe 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -21,6 +21,7 @@ use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; /** @api */ #[InstanceofDeprecated(insteadUse: 'Type::isNull()')] @@ -35,6 +36,7 @@ class NullType implements ConstantScalarType use FalseyBooleanTypeTrait; use NonGenericTypeTrait; use NonRemoveableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -416,7 +418,7 @@ public function getFiniteTypes(): array return [$this]; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new UnionType( [ diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index d3f3821bac9..d1c18cf7b4b 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -4,6 +4,7 @@ use PhpParser\Node\Identifier; use PHPStan\Analyser\OutOfClassScope; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -29,6 +30,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use function array_filter; use function array_key_exists; @@ -47,6 +49,7 @@ class ObjectShapeType implements Type use ObjectTypeTrait; use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** * @api @@ -546,7 +549,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return new self($properties, $this->optionalProperties); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { if (!$exponent instanceof NeverType && !$this->isSuperTypeOf($exponent)->no()) { return TypeCombinator::union($this, $exponent); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index b358bf41754..f3bff0aab23 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -53,6 +53,7 @@ use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\SubstractableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use Stringable; @@ -81,6 +82,7 @@ class ObjectType implements TypeWithClassName, SubtractableType use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; use SubstractableTypeTrait; + use OperatorTypeTrait; private const DESCRIPTION_CACHE_LIMIT = 1024; @@ -2120,7 +2122,7 @@ public function getFiniteTypes(): array return $this->getEnumCases(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { $object = new ObjectWithoutClassType(); if (!$exponent instanceof NeverType && !$object->isSuperTypeOf($this)->no() && !$object->isSuperTypeOf($exponent)->no()) { diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 5a3f5fadd88..74f3f129c26 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type; use PhpParser\Node\Identifier; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Turbo\ShadowedByTurboExtension; @@ -10,6 +11,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\SubstractableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -24,6 +26,7 @@ class ObjectWithoutClassType implements SubtractableType use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; use SubstractableTypeTrait; + use OperatorTypeTrait; private ?Type $subtractedType; @@ -204,7 +207,7 @@ public function tryRemove(Type $typeToRemove): ?Type return $this->subtract($typeToRemove); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { if (!$exponent instanceof NeverType && !$this->isSuperTypeOf($exponent)->no()) { return TypeCombinator::union($this, $exponent); diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php new file mode 100644 index 00000000000..8bc0fb7309e --- /dev/null +++ b/src/Type/OperatorTypeHelper.php @@ -0,0 +1,1690 @@ +toString(); + $rightStringType = $right->toString(); + if (TypeCombinator::union( + $leftStringType, + $rightStringType, + ) instanceof ErrorType) { + return new ErrorType(); + } + + if ($leftStringType instanceof ConstantStringType && $leftStringType->getValue() === '') { + return $rightStringType; + } + + if ($rightStringType instanceof ConstantStringType && $rightStringType->getValue() === '') { + return $leftStringType; + } + + if ($leftStringType instanceof ConstantStringType && $rightStringType instanceof ConstantStringType) { + return $leftStringType->append($rightStringType); + } + + $leftConstantStrings = $leftStringType->getConstantStrings(); + $rightConstantStrings = $rightStringType->getConstantStrings(); + $combinedConstantStringsCount = count($leftConstantStrings) * count($rightConstantStrings); + + // we limit the number of union-types for performance reasons + if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + $strings = []; + + foreach ($leftConstantStrings as $leftConstantString) { + if ($leftConstantString->getValue() === '') { + $strings = array_merge($strings, $rightConstantStrings); + + continue; + } + + foreach ($rightConstantStrings as $rightConstantString) { + if ($rightConstantString->getValue() === '') { + $strings[] = $leftConstantString; + + continue; + } + + $strings[] = $leftConstantString->append($rightConstantString); + } + } + + if (count($strings) > 0) { + return TypeCombinator::union(...$strings); + } + } + + $accessoryTypes = []; + if ($leftStringType->isNonEmptyString()->and($rightStringType->isNonEmptyString())->yes()) { + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } elseif ($leftStringType->isNonFalsyString()->or($rightStringType->isNonFalsyString())->yes()) { + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } elseif ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + + if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + + if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } + + if ($leftStringType->isUppercaseString()->and($rightStringType->isUppercaseString())->yes()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } + + $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); + if ($leftNumericStringNonEmpty->isNumericString()->yes()) { + $validationCallback = $left->isInteger()->yes() + ? static fn (string $value): bool => !str_starts_with($value, '-') + : static fn (string $value): bool => Strings::match($value, '#^\d+$#') !== null; + + $allRightConstantsZeroOrMore = false; + foreach ($rightConstantStrings as $rightConstantString) { + if ($rightConstantString->getValue() === '') { + continue; + } + + if ( + !is_numeric($rightConstantString->getValue()) + || !$validationCallback($rightConstantString->getValue()) + ) { + $allRightConstantsZeroOrMore = false; + break; + } + + $allRightConstantsZeroOrMore = true; + } + + $zeroOrMoreInteger = IntegerRangeType::fromInterval(0, null); + $nonNegativeRight = $allRightConstantsZeroOrMore || $zeroOrMoreInteger->isSuperTypeOf($right)->yes(); + if ($nonNegativeRight) { + $accessoryTypes[] = new AccessoryNumericStringType(); + } + } + + if (count($accessoryTypes) > 0) { + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); + } + + return new StringType(); + } + + public static function bitwiseAnd(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + $result = self::getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a & $b); + if ($result instanceof Type) { + return $result; + } + + // computed before optimizeScalarType() below widens integer ranges to int + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + + if ($result === self::IS_SCALAR_TYPE) { + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + $leftIsString = $leftType->isString(); + $rightIsString = $rightType->isString(); + if ( + ($leftIsString->yes() || $leftType instanceof MixedType) + && ($rightIsString->yes() || $rightType instanceof MixedType) + ) { + return new StringType(); + } + if ($leftIsString->maybe() && $rightIsString->maybe()) { + return new ErrorType(); + } + + if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { + return new ErrorType(); + } + + $bitwiseAndRange = self::computeBitwiseAndRange($leftNumberType, $rightNumberType); + if ($bitwiseAndRange !== null) { + return $bitwiseAndRange; + } + + return new IntegerType(); + } + + public static function bitwiseOr(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + $result = self::getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a | $b); + if ($result instanceof Type) { + return $result; + } + + // computed before optimizeScalarType() below widens integer ranges to int + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + + if ($result === self::IS_SCALAR_TYPE) { + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + $leftIsString = $leftType->isString(); + $rightIsString = $rightType->isString(); + if ( + ($leftIsString->yes() || $leftType instanceof MixedType) + && ($rightIsString->yes() || $rightType instanceof MixedType) + ) { + return new StringType(); + } + if ($leftIsString->maybe() && $rightIsString->maybe()) { + return new ErrorType(); + } + + if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { + return new ErrorType(); + } + + $bitwiseOrRange = self::computeBitwiseOrXorRange($leftNumberType, $rightNumberType); + if ($bitwiseOrRange !== null) { + return $bitwiseOrRange; + } + + return new IntegerType(); + } + + public static function bitwiseXor(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + $result = self::getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a ^ $b); + if ($result instanceof Type) { + return $result; + } + + // computed before optimizeScalarType() below widens integer ranges to int + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + + if ($result === self::IS_SCALAR_TYPE) { + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + + $leftIsString = $leftType->isString(); + $rightIsString = $rightType->isString(); + if ( + ($leftIsString->yes() || $leftType instanceof MixedType) + && ($rightIsString->yes() || $rightType instanceof MixedType) + ) { + return new StringType(); + } + if ($leftIsString->maybe() && $rightIsString->maybe()) { + return new ErrorType(); + } + + if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { + return new ErrorType(); + } + + $bitwiseXorRange = self::computeBitwiseOrXorRange($leftNumberType, $rightNumberType); + if ($bitwiseXorRange !== null) { + return $bitwiseXorRange; + } + + return new IntegerType(); + } + + /** + * @param callable(bool|float|int|string|null, bool|float|int|string|null):string $operationCallable + * + * @return self::IS_UNKNOWN|self::IS_SCALAR_TYPE|Type + */ + private static function getFiniteOrConstantScalarTypes(Type $leftType, Type $rightType, callable $operationCallable): int|Type + { + if ($leftType instanceof IntegerRangeType) { + $leftTypes = $leftType->getFiniteTypes(); + } else { + $leftTypes = $leftType->getConstantScalarTypes(); + } + if ($rightType instanceof IntegerRangeType) { + $rightTypes = $rightType->getFiniteTypes(); + } else { + $rightTypes = $rightType->getConstantScalarTypes(); + } + + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + + if ($leftTypesCount === 0 || $rightTypesCount === 0) { + return self::IS_UNKNOWN; + } + + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if ($generalize) { + return self::IS_SCALAR_TYPE; + } + + $resultTypes = []; + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { + $resultValue = $operationCallable($leftTypeInner->getValue(), $rightTypeInner->getValue()); + $resultType = self::getTypeFromValue($resultValue); + } else { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + $resultValue = $operationCallable($leftNumberType->getValue(), $rightNumberType->getValue()); + $resultType = self::getTypeFromValue($resultValue); + } + $resultTypes[] = $resultType; + } + } + return TypeCombinator::union(...$resultTypes); + } + + public static function divide(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + $result = self::getDivTypeFromTypes($leftType, $rightType); + + if ($leftType->isInteger()->yes() && $rightType->isInteger()->yes()) { + $modType = self::modulo($leftType, $rightType, $phpVersion); + if ($modType->isInteger()->yes() && (new ConstantIntegerType(0))->isSuperTypeOf($modType)->yes()) { + $withoutFloat = TypeCombinator::remove($result, new FloatType()); + + // PHP_INT_MIN / -1 divides without a remainder but still overflows to a float + if (!$withoutFloat instanceof NeverType) { + return $withoutFloat; + } + } + } + + return $result; + } + + private static function getDivTypeFromTypes(Type $leftType, Type $rightType): Type + { + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + if (in_array($rightNumberType->getValue(), [0, 0.0], true)) { + return new ErrorType(); + } + + $resultType = self::getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid + $resultTypes[] = $resultType; + } + } + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + $rightScalarValues = $rightType->toNumber()->getConstantScalarValues(); + foreach ($rightScalarValues as $scalarValue) { + if (in_array($scalarValue, [0, 0.0], true)) { + return new ErrorType(); + } + } + + return self::resolveCommonMath('/', $leftType, $rightType); + } + + public static function modulo(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { + return new ErrorType(); + } + + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + $rightIntegerValue = (int) $rightNumberType->getValue(); + if ($rightIntegerValue === 0) { + return new ErrorType(); + } + + $resultType = self::getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue); + $resultTypes[] = $resultType; + } + } + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + $integerType = $rightType->toInteger(); + if ($integerType instanceof ConstantIntegerType && $integerType->getValue() === 1) { + return new ConstantIntegerType(0); + } + + $rightScalarValues = $rightType->toNumber()->getConstantScalarValues(); + foreach ($rightScalarValues as $scalarValue) { + + if (in_array($scalarValue, [0, 0.0], true)) { + return new ErrorType(); + } + } + + $maxMagnitude = null; + $divisorBounds = self::getIntegerBounds($rightType->toInteger()); + if ($divisorBounds !== null) { + $maxMagnitude = self::getMaxModuloMagnitude($divisorBounds[0], $divisorBounds[1]); + } + + [$leftMin, $leftMax] = self::getIntegerBounds($leftType->toInteger()) ?? [null, null]; + if ($leftMin === null && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()) { + $leftMin = 0; + } + if ($leftMax === null && IntegerRangeType::fromInterval(null, 0)->isSuperTypeOf($leftType)->yes()) { + $leftMax = 0; + } + + // The result has the sign of the dividend and is never bigger in magnitude than either + // the dividend or the largest magnitude the divisor can have. + if ($leftMax !== null && $leftMax <= 0) { + $rangeMax = 0; + } elseif ($maxMagnitude === null) { + $rangeMax = $leftMax; + } elseif ($leftMax === null) { + $rangeMax = $maxMagnitude; + } else { + $rangeMax = min($leftMax, $maxMagnitude); + } + + if ($leftMin !== null && $leftMin >= 0) { + $rangeMin = 0; + } elseif ($maxMagnitude === null) { + $rangeMin = $leftMin; + } elseif ($leftMin === null) { + $rangeMin = -$maxMagnitude; + } else { + $rangeMin = max($leftMin, -$maxMagnitude); + } + + return IntegerRangeType::fromInterval($rangeMin, $rangeMax); + } + + /** + * Highest possible absolute value of `$x % $divisor`, which is 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 as unbounded too: `abs(PHP_INT_MIN)` does not + * fit into an integer, and the bound it stands for is PHP_INT_MAX, which is all an unbounded + * result can hold anyway. + */ + private static function getMaxModuloMagnitude(?int $divisorMin, ?int $divisorMax): ?int + { + if ($divisorMin === null || $divisorMax === null || $divisorMin === PHP_INT_MIN) { + return null; + } + + $magnitude = max(abs($divisorMin), abs($divisorMax)); + if ($magnitude === 0) { + return null; + } + + return $magnitude - 1; + } + + /** + * @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. + */ + private static function getIntegerBounds(Type $type): ?array + { + $innerTypes = $type instanceof UnionType ? $type->getTypes() : [$type]; + if ($innerTypes === []) { + return null; + } + + $min = null; + $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; + } + + if ($innerMin === null) { + $unboundedMin = true; + } elseif ($min === null || $innerMin < $min) { + $min = $innerMin; + } + + if ($innerMax === null) { + $unboundedMax = true; + } elseif ($max === null || $innerMax > $max) { + $max = $innerMax; + } + } + + return [ + $unboundedMin ? null : $min, + $unboundedMax ? null : $max, + ]; + } + + public static function add(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + $resultType = self::getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue()); + $resultTypes[] = $resultType; + } + } + + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + $arrayUnion = self::tryArrayUnion($leftType, $rightType); + if ($arrayUnion !== null) { + return $arrayUnion; + } + + $leftIsArray = $leftType->isArray(); + $rightIsArray = $rightType->isArray(); + + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + if ($leftIsArray->no() && $rightIsArray->no()) { + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + ]); + } + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + new ArrayType(new MixedType(), new MixedType()), + ]); + } + + if ( + ($leftIsArray->yes() && $rightIsArray->no()) + || ($leftIsArray->no() && $rightIsArray->yes()) + ) { + return new ErrorType(); + } + + if ( + ($leftIsArray->yes() && $rightIsArray->maybe()) + || ($leftIsArray->maybe() && $rightIsArray->yes()) + ) { + $resultType = new ArrayType(new MixedType(), new MixedType()); + if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { + return TypeCombinator::intersect($resultType, new NonEmptyArrayType()); + } + + return $resultType; + } + + if ($leftIsArray->maybe() && $rightIsArray->maybe()) { + $plusable = new UnionType([ + new StringType(), + new FloatType(), + new IntegerType(), + new ArrayType(new MixedType(), new MixedType()), + new BooleanType(), + ]); + + $plusableSuperTypeOfLeft = $plusable->isSuperTypeOf($leftType)->yes(); + $plusableSuperTypeOfRight = $plusable->isSuperTypeOf($rightType)->yes(); + if ($plusableSuperTypeOfLeft && $plusableSuperTypeOfRight) { + return TypeCombinator::union($leftType, $rightType); + } + if ($plusableSuperTypeOfLeft && $rightType instanceof MixedType) { + return $leftType; + } + if ($plusableSuperTypeOfRight && $leftType instanceof MixedType) { + return $rightType; + } + } + + return self::resolveCommonMath('+', $leftType, $rightType); + } + + public static function arrayUnion(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + return self::tryArrayUnion($leftType, $rightType) ?? new ErrorType(); + } + + private static function tryArrayUnion(Type $leftType, Type $rightType): ?Type + { + $leftConstantArrays = $leftType->getConstantArrays(); + $rightConstantArrays = $rightType->getConstantArrays(); + + $leftCount = count($leftConstantArrays); + $rightCount = count($rightConstantArrays); + if ($leftCount > 0 && $rightCount > 0 + && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) { + $resultTypes = []; + foreach ($rightConstantArrays as $rightConstantArray) { + foreach ($leftConstantArrays as $leftConstantArray) { + $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); + foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) { + $optional = $leftConstantArray->isOptionalKey($i); + $valueType = $leftConstantArray->getOffsetValueType($leftKeyType); + if (!$optional) { + if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) { + $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType)); + } + } + $newArrayBuilder->setOffsetValueType( + $leftKeyType, + $valueType, + $optional, + ); + } + $resultTypes[] = $newArrayBuilder->getArray(); + } + } + return TypeCombinator::union(...$resultTypes); + } + + // `array{...} + array` keeps the known keyed prefix and + // folds the right-hand side into an open `...` tail. This + // only applies when the left arrays' sealedness is known (bleeding edge); + // otherwise the legacy behavior below is kept for backward compatibility. + if ($leftCount > 0 && $rightCount === 0 + && $leftCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT + && $rightType->isArray()->yes()) { + $allSealednessKnown = true; + foreach ($leftConstantArrays as $leftConstantArray) { + if ($leftConstantArray->isUnsealed()->maybe()) { + $allSealednessKnown = false; + break; + } + } + + if ($allSealednessKnown) { + $rightKeyType = $rightType->getIterableKeyType(); + $rightValueType = $rightType->getIterableValueType(); + $resultTypes = []; + foreach ($leftConstantArrays as $leftConstantArray) { + $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($leftConstantArray); + $existingUnsealed = $leftConstantArray->getUnsealedTypes(); + if ($leftConstantArray->isUnsealed()->yes() && $existingUnsealed !== null) { + $newArrayBuilder->makeUnsealed( + TypeCombinator::union($existingUnsealed[0], $rightKeyType), + TypeCombinator::union($existingUnsealed[1], $rightValueType), + ); + } else { + $newArrayBuilder->makeUnsealed($rightKeyType, $rightValueType); + } + $resultTypes[] = $newArrayBuilder->getArray(); + } + return TypeCombinator::union(...$resultTypes); + } + } + + $leftIsArray = $leftType->isArray(); + $rightIsArray = $rightType->isArray(); + if ($leftIsArray->yes() && $rightIsArray->yes()) { + if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { + // to preserve BenevolentUnionType + $keyType = $leftType->getIterableKeyType(); + } else { + $keyTypes = []; + foreach ([ + $leftType->getIterableKeyType(), + $rightType->getIterableKeyType(), + ] as $keyType) { + $keyTypes[] = $keyType; + } + $keyType = TypeCombinator::union(...$keyTypes); + } + + $leftIterableValueType = $leftType->getIterableValueType(); + $arrayType = new ArrayType( + $keyType, + TypeCombinator::union($leftIterableValueType, $rightType->getIterableValueType()), + ); + + $accessories = []; + if ($leftCount > 0) { + // Use the first constant array as a reference to list potential offsets. + // We only need to check the first array because we're looking for offsets that exist in ALL arrays. + $constantArray = $leftConstantArrays[0]; + foreach ($constantArray->getKeyTypes() as $offsetType) { + if (!$leftType->hasOffsetValueType($offsetType)->yes()) { + continue; + } + + $valueType = $leftType->getOffsetValueType($offsetType); + $accessories[] = new HasOffsetValueType($offsetType, $valueType); + } + } + + if ($rightCount > 0) { + // Use the first constant array as a reference to list potential offsets. + // We only need to check the first array because we're looking for offsets that exist in ALL arrays. + $constantArray = $rightConstantArrays[0]; + foreach ($constantArray->getKeyTypes() as $offsetType) { + if (!$rightType->hasOffsetValueType($offsetType)->yes()) { + continue; + } + + $valueType = TypeCombinator::union($leftIterableValueType, $rightType->getOffsetValueType($offsetType)); + $accessories[] = new HasOffsetValueType($offsetType, $valueType); + } + } + + if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { + $accessories[] = new NonEmptyArrayType(); + } + if ($leftType->isList()->yes() && $rightType->isList()->yes()) { + $accessories[] = new AccessoryArrayListType(); + } + + if (count($accessories) > 0) { + $arrayType = TypeCombinator::intersect($arrayType, ...$accessories); + } + + return $arrayType; + } + + return null; + } + + public static function subtract(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + $resultType = self::getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue()); + $resultTypes[] = $resultType; + } + } + + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + return self::resolveCommonMath('-', $leftType, $rightType); + } + + public static function multiply(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + $resultType = self::getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue()); + $resultTypes[] = $resultType; + } + } + + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + $leftNumberType = $leftType->toNumber(); + if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) { + if ($rightType->isFloat()->yes()) { + return new ConstantFloatType(0.0); + } + return new ConstantIntegerType(0); + } + $rightNumberType = $rightType->toNumber(); + if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) { + if ($leftType->isFloat()->yes()) { + return new ConstantFloatType(0.0); + } + return new ConstantIntegerType(0); + } + + return self::resolveCommonMath('*', $leftType, $rightType); + } + + public static function shiftLeft(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + if ($rightNumberType->getValue() < 0) { + return new ErrorType(); + } + + $resultType = self::getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue())); + $resultTypes[] = $resultType; + } + } + + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + return self::resolveCommonMath('<<', $leftType, $rightType); + } + + public static function shiftRight(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type + { + if ($leftType instanceof NeverType || $rightType instanceof NeverType) { + return self::getNeverType($leftType, $rightType); + } + + $leftTypes = $leftType->getConstantScalarTypes(); + $rightTypes = $rightType->getConstantScalarTypes(); + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { + $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } + + if ($rightNumberType->getValue() < 0) { + return new ErrorType(); + } + + $resultType = self::getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue())); + $resultTypes[] = $resultType; + } + } + + return TypeCombinator::union(...$resultTypes); + } + + $leftType = self::optimizeScalarType($leftType); + $rightType = self::optimizeScalarType($rightType); + } + + $leftNumberType = $leftType->toNumber(); + $rightNumberType = $rightType->toNumber(); + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + + return self::resolveCommonMath('>>', $leftType, $rightType); + } + + private static function optimizeScalarType(Type $type): Type + { + $types = []; + if ($type->isInteger()->yes()) { + $types[] = new IntegerType(); + } + if ($type->isString()->yes()) { + $types[] = new StringType(); + } + if ($type->isFloat()->yes()) { + $types[] = new FloatType(); + } + if ($type->isNull()->yes()) { + $types[] = new NullType(); + } + + if (count($types) === 0) { + return new ErrorType(); + } + + if (count($types) === 1) { + return $types[0]; + } + + return new UnionType($types); + } + + /** + * @return array{int, int|null}|null [min, max] of a type known to be a non-negative integer, + * with a null max when it has no finite upper bound. Null when the type is not known + * to be a non-negative integer. + */ + private static function getNonNegativeIntegerBounds(Type $type): ?array + { + if ($type instanceof IntegerRangeType) { + $min = $type->getMin(); + if ($min !== null && $min >= 0) { + return [$min, $type->getMax()]; + } + return null; + } + if ($type instanceof ConstantIntegerType && $type->getValue() >= 0) { + return [$type->getValue(), $type->getValue()]; + } + return null; + } + + /** + * Expects the operands already converted with toNumber(). + * + * A single non-negative operand is enough to bound the result: the result's bits + * are a subset of that operand's bits, so it stays within int<0, thatMax>. + * With no finite max on any non-negative side the result is still int<0, max>. + */ + private static function computeBitwiseAndRange(Type $leftNumberType, Type $rightNumberType): ?Type + { + $leftBounds = self::getNonNegativeIntegerBounds($leftNumberType); + $rightBounds = self::getNonNegativeIntegerBounds($rightNumberType); + if ($leftBounds === null && $rightBounds === null) { + return null; + } + + $maxValues = []; + if ($leftBounds !== null && $leftBounds[1] !== null) { + $maxValues[] = $leftBounds[1]; + } + if ($rightBounds !== null && $rightBounds[1] !== null) { + $maxValues[] = $rightBounds[1]; + } + + return IntegerRangeType::fromInterval(0, $maxValues === [] ? null : min($maxValues)); + } + + /** + * Expects the operands already converted with toNumber(). + * + * Both operands have to be non-negative: they keep the sign bit of the result clear. + * Without a finite max on either side the result is still int<0, max>. + */ + private static function computeBitwiseOrXorRange(Type $leftNumberType, Type $rightNumberType): ?Type + { + $leftBounds = self::getNonNegativeIntegerBounds($leftNumberType); + $rightBounds = self::getNonNegativeIntegerBounds($rightNumberType); + if ($leftBounds === null || $rightBounds === null) { + return null; + } + if ($leftBounds[1] === null || $rightBounds[1] === null) { + return IntegerRangeType::fromInterval(0, null); + } + + return IntegerRangeType::fromInterval(0, self::allBitsMask(max($leftBounds[1], $rightBounds[1]))); + } + + /** + * Propagates the highest set bit of a non-negative value into all lower bits: + * 200 becomes 255, 10 becomes 15. + */ + private static function allBitsMask(int $value): int + { + $value |= $value >> 1; + $value |= $value >> 2; + $value |= $value >> 4; + $value |= $value >> 8; + $value |= $value >> 16; + $value |= $value >> 32; + + return $value; + } + + /** + * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator + */ + private static function resolveCommonMath(string $operator, Type $leftType, Type $rightType): 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; + } + } + + $union = TypeCombinator::union(...$unionParts); + if ($leftNumberType instanceof BenevolentUnionType) { + return TypeUtils::toBenevolentUnion($union)->toNumber(); + } + + return $union->toNumber(); + } + } + + if ( + $leftType->isArray()->yes() + || $rightType->isArray()->yes() + || $types->isArray()->yes() + ) { + return new ErrorType(); + } + + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } + if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) { + return self::getNeverType($leftNumberType, $rightNumberType); + } + + if ( + $leftNumberType->isFloat()->yes() + || $rightNumberType->isFloat()->yes() + ) { + if (in_array($operator, ['<<', '>>'], true)) { + return new IntegerType(); + } + return new FloatType(); + } + + $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); + if ($operator === '/') { + if ($types instanceof MixedType || $resultType->isInteger()->yes()) { + return new BenevolentUnionType([new IntegerType(), new FloatType()]); + } + + return new UnionType([new IntegerType(), new FloatType()]); + } + + if ($types instanceof MixedType + || $leftType instanceof BenevolentUnionType + || $rightType instanceof BenevolentUnionType + ) { + return TypeUtils::toBenevolentUnion($resultType); + } + + 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 === '-') { + 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 { + if ($rangeMin === $rangeMax && $rangeMin !== null + && ($operand->getMin() === null || $operand->getMax() === null)) { + $min = null; + $max = $rangeMin; + } else { + if ($operand->getMin() === null) { + $min = null; + } elseif ($rangeMin !== null) { + if ($operand->getMax() !== null) { + /** @var int|float $min */ + $min = $rangeMin - $operand->getMax(); + } else { + /** @var int|float $min */ + $min = $rangeMin - $operand->getMin(); + } + } else { + $min = null; + } + + if ($operand->getMax() === null) { + $min = null; + $max = null; + } elseif ($rangeMax !== null) { + if ($rangeMin !== null && $operand->getMin() === null) { + /** @var int|float $min */ + $min = $rangeMin - $operand->getMax(); + $max = null; + } elseif ($operand->getMin() !== null) { + /** @var int|float $max */ + $max = $rangeMax - $operand->getMin(); + } else { + $max = null; + } + } else { + $max = null; + } + + if ($min !== null && $max !== null && $min > $max) { + [$min, $max] = [$max, $min]; + } + } + } + } 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(); + $scalarValues = $type->getConstantScalarValues(); + + if (count($scalarValues) > 0) { + $newTypes = []; + foreach ($scalarValues as $scalarValue) { + if (is_int($scalarValue)) { + /** @var int|float $newValue */ + $newValue = -$scalarValue; + if (!is_int($newValue)) { + // Negating the smallest integer overflows into a float. + $newTypes[] = new ConstantFloatType($newValue); + continue; + } + $newTypes[] = new ConstantIntegerType($newValue); + } elseif (is_float($scalarValue)) { + $newTypes[] = new ConstantFloatType(-$scalarValue); + } + } + + return TypeCombinator::union(...$newTypes); + } + + 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; + } + + /** + * @param mixed $value + */ + private static function getTypeFromValue($value): Type + { + return ConstantTypeHelper::getTypeFromValue($value); + } + + private static function getNeverType(Type $leftType, Type $rightType): Type + { + // make sure we don't lose the explicit flag in the process + if ($leftType instanceof NeverType && $leftType->isExplicit()) { + return $leftType; + } + if ($rightType instanceof NeverType && $rightType->isExplicit()) { + return $rightType; + } + return new NeverType(); + } + + public static function negate(Type $type, PhpVersion $phpVersion): Type + { + $negated = self::negateNumber($type); + if ($negated instanceof IntegerRangeType) { + return self::multiply($type, new ConstantIntegerType(-1), $phpVersion); + } + + return $negated; + } + + /** + * The divisor's negative and positive parts are divided separately (dividing by zero throws), + * each at the corners of the operand ranges: truncated division is monotonic in both operands + * as long as the divisor keeps its sign. + */ + public static function intDivide(Type $numeratorType, Type $divisorType, PhpVersion $phpVersion): Type + { + $numeratorIntervals = self::getIntervals($numeratorType->toInteger()); + $divisorIntervals = self::getIntervals($divisorType->toInteger()); + if ($numeratorIntervals === null || $divisorIntervals === null) { + return new ErrorType(); + } + + $resultTypes = []; + foreach ($numeratorIntervals as [$numeratorMin, $numeratorMax]) { + foreach ($divisorIntervals as [$divisorMin, $divisorMax]) { + if ($divisorMin < 0) { + $resultTypes[] = self::divideIntervals($numeratorMin, $numeratorMax, $divisorMin, min($divisorMax, -1)); + } + if ($divisorMax <= 0) { + continue; + } + + $resultTypes[] = self::divideIntervals($numeratorMin, $numeratorMax, max($divisorMin, 1), $divisorMax); + } + } + + if (count($resultTypes) === 0) { + return new ErrorType(); + } + + return TypeCombinator::union(...$resultTypes); + } + + /** + * Unbounded ends are represented by -INF and INF. + * + * @return list|null + */ + private static function getIntervals(Type $type): ?array + { + $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; + } + } + + return $intervals; + } + + private static function divideIntervals(int|float $numeratorMin, int|float $numeratorMax, int|float $divisorMin, int|float $divisorMax): Type + { + $results = []; + foreach ([$numeratorMin, $numeratorMax] as $numerator) { + foreach ([$divisorMin, $divisorMax] as $divisor) { + $results[] = self::divideBounds($numerator, $divisor); + } + } + + $min = min($results); + $max = max($results); + + return IntegerRangeType::fromInterval( + is_infinite($min) ? null : (int) $min, + is_infinite($max) ? null : (int) $max, + ); + } + + private static function divideBounds(int|float $numerator, int|float $divisor): int|float + { + if (is_infinite($numerator)) { + if (is_infinite($divisor)) { + // the range already contains 0 through a neighbouring corner + return 0; + } + + return ($numerator > 0) === ($divisor > 0) ? INF : -INF; + } + + if (is_infinite($divisor)) { + return 0; + } + + if ($numerator === PHP_INT_MIN && $divisor === -1) { + // throws ArithmeticError, the closest valid result is intdiv(PHP_INT_MIN + 1, -1) + return PHP_INT_MAX; + } + + return intdiv((int) $numerator, (int) $divisor); + } + +} diff --git a/src/Type/Php/IntdivFunctionReturnTypeExtension.php b/src/Type/Php/IntdivFunctionReturnTypeExtension.php index 6611418ff30..a91eb1719c1 100644 --- a/src/Type/Php/IntdivFunctionReturnTypeExtension.php +++ b/src/Type/Php/IntdivFunctionReturnTypeExtension.php @@ -5,26 +5,23 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\ErrorType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; -use PHPStan\Type\UnionType; use function count; -use function intdiv; -use function is_infinite; -use function max; -use function min; -use const INF; -use const PHP_INT_MAX; -use const PHP_INT_MIN; +// operators take the configured PHP version, like InitializerExprTypeResolver does +// @phpstan-ignore phpstan.phpVersionInjection #[AutowiredService] final class IntdivFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private PhpVersion $phpVersion) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'intdiv'; @@ -37,100 +34,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } - $numeratorIntervals = $this->getIntervals($scope->getType($args[0]->value)->toInteger()); - $divisorIntervals = $this->getIntervals($scope->getType($args[1]->value)->toInteger()); - if ($numeratorIntervals === null || $divisorIntervals === null) { + $resultType = $scope->getType($args[0]->value)->arithmeticIntDivide($scope->getType($args[1]->value), $this->phpVersion); + if ($resultType instanceof ErrorType) { return null; } - $resultTypes = []; - foreach ($numeratorIntervals as [$numeratorMin, $numeratorMax]) { - foreach ($divisorIntervals as [$divisorMin, $divisorMax]) { - // division by zero throws, so only the negative and the positive part of the divisor produce a result - if ($divisorMin < 0) { - $resultTypes[] = $this->divideIntervals($numeratorMin, $numeratorMax, $divisorMin, min($divisorMax, -1)); - } - if ($divisorMax <= 0) { - continue; - } - - $resultTypes[] = $this->divideIntervals($numeratorMin, $numeratorMax, max($divisorMin, 1), $divisorMax); - } - } - - if (count($resultTypes) === 0) { - return null; - } - - return TypeCombinator::union(...$resultTypes); - } - - /** - * Unbounded ends are represented by -INF and INF. - * - * @return list|null - */ - private function getIntervals(Type $type): ?array - { - $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; - } - } - - return $intervals; - } - - /** - * The divisor interval must not contain zero. The quotient is then monotonic in both operands - * and truncation towards zero is monotonic too, so the extremes are found in the corners. - */ - private function divideIntervals(int|float $numeratorMin, int|float $numeratorMax, int|float $divisorMin, int|float $divisorMax): Type - { - $results = []; - foreach ([$numeratorMin, $numeratorMax] as $numerator) { - foreach ([$divisorMin, $divisorMax] as $divisor) { - $results[] = $this->divideBounds($numerator, $divisor); - } - } - - $min = min($results); - $max = max($results); - - return IntegerRangeType::fromInterval( - is_infinite($min) ? null : (int) $min, - is_infinite($max) ? null : (int) $max, - ); - } - - private function divideBounds(int|float $numerator, int|float $divisor): int|float - { - if (is_infinite($numerator)) { - if (is_infinite($divisor)) { - // the range already contains 0 through a neighbouring corner - return 0; - } - - return ($numerator > 0) === ($divisor > 0) ? INF : -INF; - } - - if (is_infinite($divisor)) { - return 0; - } - - if ($numerator === PHP_INT_MIN && $divisor === -1) { - // throws ArithmeticError, the closest valid result is intdiv(PHP_INT_MIN + 1, -1) - return PHP_INT_MAX; - } - - return intdiv((int) $numerator, (int) $divisor); + return $resultType; } } diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index 514fec24d39..00afffd372e 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -17,6 +17,7 @@ use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -36,6 +37,7 @@ class ResourceType implements Type use NonOffsetAccessibleTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -117,7 +119,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 5692b9aa483..d8018a55b70 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -25,6 +25,7 @@ use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use function get_class; use function sprintf; @@ -37,6 +38,7 @@ class StaticType implements TypeWithClassName, SubtractableType use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; private ?Type $subtractedType; @@ -919,9 +921,9 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return $this->getStaticObjectType()->exponentiate($exponent); + return $this->getStaticObjectType()->arithmeticPower($exponent, $phpVersion); } public function getFiniteTypes(): array diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 686dcadb917..017e284c7d3 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -25,6 +25,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/StrictMixedType.cpp')] @@ -36,6 +37,7 @@ class StrictMixedType implements CompoundType use NonIterableTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; public function getReferencedClasses(): array { @@ -508,7 +510,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index d040a563c5a..d885c1dd125 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -23,6 +23,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use function count; @@ -42,6 +43,7 @@ class StringType implements Type use UndecidedComparisonTypeTrait; use NonGenericTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -343,7 +345,7 @@ public function getFiniteTypes(): array return []; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return ExponentiateHelper::exponentiate($this, $exponent); } diff --git a/src/Type/Traits/ArrayTypeTrait.php b/src/Type/Traits/ArrayTypeTrait.php index db527c5b697..06a259eb62b 100644 --- a/src/Type/Traits/ArrayTypeTrait.php +++ b/src/Type/Traits/ArrayTypeTrait.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Traits; +use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -15,6 +16,8 @@ trait ArrayTypeTrait { + use OperatorTypeTrait; + public function isArray(): TrinaryLogic { return TrinaryLogic::createYes(); @@ -200,7 +203,7 @@ public function isScalar(): TrinaryLogic return TrinaryLogic::createNo(); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 55ebcedc73b..89fcdbd90cf 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -27,6 +27,8 @@ trait LateResolvableTypeTrait { + use OperatorTypeTrait; + private ?Type $result = null; public function getObjectClassNames(): array @@ -692,9 +694,9 @@ public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): T return $otherType->isSmallerThanOrEqual($result, $phpVersion); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return $this->resolve()->exponentiate($exponent); + return $this->resolve()->arithmeticPower($exponent, $phpVersion); } public function getFiniteTypes(): array diff --git a/src/Type/Traits/OperatorTypeTrait.php b/src/Type/Traits/OperatorTypeTrait.php new file mode 100644 index 00000000000..7806358458c --- /dev/null +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -0,0 +1,88 @@ +arithmeticPower($exponent, new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION)); + } + + public function arithmeticAdd(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::add($this, $right, $phpVersion); + } + + public function arithmeticSubtract(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::subtract($this, $right, $phpVersion); + } + + public function arithmeticMultiply(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::multiply($this, $right, $phpVersion); + } + + public function arithmeticDivide(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::divide($this, $right, $phpVersion); + } + + public function arithmeticIntDivide(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::intDivide($this, $right, $phpVersion); + } + + public function arithmeticModulo(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::modulo($this, $right, $phpVersion); + } + + public function arithmeticNegate(PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::negate($this, $phpVersion); + } + + public function bitwiseAnd(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::bitwiseAnd($this, $right, $phpVersion); + } + + public function bitwiseOr(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::bitwiseOr($this, $right, $phpVersion); + } + + public function bitwiseXor(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::bitwiseXor($this, $right, $phpVersion); + } + + public function bitwiseShiftLeft(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::shiftLeft($this, $right, $phpVersion); + } + + public function bitwiseShiftRight(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::shiftRight($this, $right, $phpVersion); + } + + public function stringConcatenate(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::concat($this, $right, $phpVersion); + } + + public function arrayUnion(Type $right, PhpVersion $phpVersion): Type + { + return OperatorTypeHelper::arrayUnion($this, $right, $phpVersion); + } + +} diff --git a/src/Type/Type.php b/src/Type/Type.php index 3abdfca8a85..5a22fcce1b5 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -386,9 +386,61 @@ public function getEnumCaseObject(): ?EnumCaseObjectType; */ public function getFiniteTypes(): array; - /** Models the ** operator. */ + /** + * Models the ** operator. + * + * @deprecated Use arithmeticPower() instead. + */ public function exponentiate(Type $exponent): Type; + /** + * Models the + operator on numbers. The operators of these methods return ErrorType + * for an invalid operation, they do not call OperatorTypeSpecifyingExtensions. + */ + public function arithmeticAdd(Type $right, PhpVersion $phpVersion): Type; + + /** Models the - operator. */ + public function arithmeticSubtract(Type $right, PhpVersion $phpVersion): Type; + + /** Models the * operator. */ + public function arithmeticMultiply(Type $right, PhpVersion $phpVersion): Type; + + /** Models the / operator. */ + public function arithmeticDivide(Type $right, PhpVersion $phpVersion): Type; + + /** Models intdiv(). */ + public function arithmeticIntDivide(Type $right, PhpVersion $phpVersion): Type; + + /** Models the % operator. */ + public function arithmeticModulo(Type $right, PhpVersion $phpVersion): Type; + + /** Models the ** operator. */ + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type; + + /** Models the unary - operator. */ + public function arithmeticNegate(PhpVersion $phpVersion): Type; + + /** Models the & operator. */ + public function bitwiseAnd(Type $right, PhpVersion $phpVersion): Type; + + /** Models the | operator. */ + public function bitwiseOr(Type $right, PhpVersion $phpVersion): Type; + + /** Models the ^ operator. */ + public function bitwiseXor(Type $right, PhpVersion $phpVersion): Type; + + /** Models the << operator. */ + public function bitwiseShiftLeft(Type $right, PhpVersion $phpVersion): Type; + + /** Models the >> operator. */ + public function bitwiseShiftRight(Type $right, PhpVersion $phpVersion): Type; + + /** Models the . operator. */ + public function stringConcatenate(Type $right, PhpVersion $phpVersion): Type; + + /** Models the + operator on arrays. */ + public function arrayUnion(Type $right, PhpVersion $phpVersion): Type; + public function isCallable(): TrinaryLogic; /** @return list */ diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index b992e150034..f76a7fa5710 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -37,6 +37,7 @@ use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\Generic\UnresolvedTemplateArgumentType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use Throwable; use function array_diff_assoc; use function array_fill_keys; @@ -59,6 +60,7 @@ class UnionType implements CompoundType { use NonGeneralizableTypeTrait; + use OperatorTypeTrait; public const EQUAL_UNION_CLASSES = [ DateTimeInterface::class => [DateTimeImmutable::class, DateTime::class], @@ -1653,9 +1655,9 @@ public function tryRemove(Type $typeToRemove): ?Type return new UnionType($innerTypes); } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->exponentiate($exponent)); + return $this->unionTypes(static fn (Type $type): Type => $type->arithmeticPower($exponent, $phpVersion)); } public function getFiniteTypes(): array diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index e38dcf218d1..7f9386e6ce7 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -17,6 +17,7 @@ use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use PHPStan\Type\Traits\OperatorTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; /** @api */ @@ -35,6 +36,7 @@ class VoidType implements Type use UndecidedComparisonTypeTrait; use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + use OperatorTypeTrait; /** @api */ public function __construct() @@ -268,7 +270,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public function exponentiate(Type $exponent): Type + public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { return new ErrorType(); } diff --git a/tests/PHPStan/Analyser/nsrt/bcmath-number.php b/tests/PHPStan/Analyser/nsrt/bcmath-number.php index 0b61cfe2c42..841c672d53a 100644 --- a/tests/PHPStan/Analyser/nsrt/bcmath-number.php +++ b/tests/PHPStan/Analyser/nsrt/bcmath-number.php @@ -183,7 +183,7 @@ public function bcVsNull(Number $a): void $b = null; assertType('*ERROR*', $a + $b); assertType('*ERROR*', $a - $b); - assertType('0', $a * $b); // BUG: This throws type error, but getMulType assumes that since null (mostly) behaves like zero, it will be zero. + assertType('*ERROR*', $a * $b); assertType('*ERROR*', $a / $b); assertType('*ERROR*', $a % $b); assertType('non-empty-string&numeric-string', $a . $b); diff --git a/turbo-ext/src/AccessoryArrayListType.cpp b/turbo-ext/src/AccessoryArrayListType.cpp index 88bc4b7c07f..d6d1adda1a6 100644 --- a/turbo-ext/src/AccessoryArrayListType.cpp +++ b/turbo-ext/src/AccessoryArrayListType.cpp @@ -321,9 +321,9 @@ static void ZEND_FASTCALL aalError0(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } -static void ZEND_FASTCALL aalError1(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL aalError2(INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); } @@ -556,7 +556,7 @@ PT_MINIT_REGISTRATION(pt_register_accessory_array_list_type) cls.method("traverse", reg::Public, 1, { reg::callableArg("cb") }, pt_type_identity_traverse_handler(), &ptret::type); cls.op(PT_OP_TRAVERSE, PT_OP_LAMBDA { return pt_op_traverse_identity(self); }); cls.method(sigs::traverseSimultaneously, aalThis2); - cls.method(sigs::exponentiate, aalError1); + cls.method(sigs::arithmeticPower, aalError2); cls.method(sigs::getFiniteTypes, aalEmptyArray0); cls.method<&AccessoryArrayListType::getDefaultBaseType>(sigs::getDefaultBaseType); diff --git a/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp b/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp index 334d0dc21d7..434de3ca46f 100644 --- a/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp +++ b/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp @@ -400,7 +400,7 @@ class AccessoryDecimalIntegerStringType static zv::Val generalize() { return pt_type_new_string_type(); } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('non-decimal-int-string' / 'decimal-int-string'); * UNDEF = pending exception */ @@ -728,9 +728,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_decimal_integer_string_type) cls.method(sigs::traverseSimultaneously, adisThis2); cls.method(sigs::generalize, adisString1); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryDecimalIntegerStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryDecimalIntegerStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, adisEmptyArray0); diff --git a/turbo-ext/src/AccessoryLiteralStringType.cpp b/turbo-ext/src/AccessoryLiteralStringType.cpp index 4e38e228424..fbb84361776 100644 --- a/turbo-ext/src/AccessoryLiteralStringType.cpp +++ b/turbo-ext/src/AccessoryLiteralStringType.cpp @@ -162,7 +162,7 @@ class AccessoryLiteralStringType static zv::Val generalize() { return pt_type_new_string_type(); } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('literal-string') */ static zv::Val toPhpDocNode() { return pt_type_new_identifier_type_node(PT_LC("literal-string")); } @@ -446,9 +446,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_literal_string_type) cls.method(sigs::traverseSimultaneously, alsThis2); cls.method(sigs::generalize, alsString1); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryLiteralStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryLiteralStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, alsEmptyArray0); diff --git a/turbo-ext/src/AccessoryLowercaseStringType.cpp b/turbo-ext/src/AccessoryLowercaseStringType.cpp index 39fb3964b59..b811672f5c7 100644 --- a/turbo-ext/src/AccessoryLowercaseStringType.cpp +++ b/turbo-ext/src/AccessoryLowercaseStringType.cpp @@ -195,7 +195,7 @@ class AccessoryLowercaseStringType static zv::Val generalize() { return pt_type_new_string_type(); } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('lowercase-string') */ static zv::Val toPhpDocNode() { return pt_type_new_identifier_type_node(PT_LC("lowercase-string")); } @@ -475,9 +475,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_lowercase_string_type) cls.method(sigs::traverseSimultaneously, alcsThis2); cls.method(sigs::generalize, alcsString1); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryLowercaseStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryLowercaseStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, alcsEmptyArray0); diff --git a/turbo-ext/src/AccessoryNonEmptyStringType.cpp b/turbo-ext/src/AccessoryNonEmptyStringType.cpp index bf9685ee62b..889dd87c394 100644 --- a/turbo-ext/src/AccessoryNonEmptyStringType.cpp +++ b/turbo-ext/src/AccessoryNonEmptyStringType.cpp @@ -197,7 +197,7 @@ class AccessoryNonEmptyStringType } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('non-empty-string') */ static zv::Val toPhpDocNode() { return pt_type_new_identifier_type_node(PT_LC("non-empty-string")); } @@ -473,9 +473,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_non_empty_string_type) cls.method<&AccessoryNonEmptyStringType::tryRemove, zp::Obj>(sigs::tryRemove); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryNonEmptyStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryNonEmptyStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, anesEmptyArray0); diff --git a/turbo-ext/src/AccessoryNonFalsyStringType.cpp b/turbo-ext/src/AccessoryNonFalsyStringType.cpp index 36b8b8a0288..537aed2e734 100644 --- a/turbo-ext/src/AccessoryNonFalsyStringType.cpp +++ b/turbo-ext/src/AccessoryNonFalsyStringType.cpp @@ -202,7 +202,7 @@ class AccessoryNonFalsyStringType static zv::Val generalize() { return pt_type_new_string_type(); } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('non-falsy-string') */ static zv::Val toPhpDocNode() { return pt_type_new_identifier_type_node(PT_LC("non-falsy-string")); } @@ -468,9 +468,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_non_falsy_string_type) cls.method(sigs::traverseSimultaneously, anfsThis2); cls.method(sigs::generalize, anfsString1); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryNonFalsyStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryNonFalsyStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, anfsEmptyArray0); diff --git a/turbo-ext/src/AccessoryNumericStringType.cpp b/turbo-ext/src/AccessoryNumericStringType.cpp index 94808d2e7b3..520c76bd4ca 100644 --- a/turbo-ext/src/AccessoryNumericStringType.cpp +++ b/turbo-ext/src/AccessoryNumericStringType.cpp @@ -245,7 +245,7 @@ class AccessoryNumericStringType } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('numeric-string') */ static zv::Val toPhpDocNode() { return pt_type_new_identifier_type_node(PT_LC("numeric-string")); } @@ -525,9 +525,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_numeric_string_type) pt_ans_one_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, &AccessoryNumericStringType::tryRemove); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryNumericStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryNumericStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, ansEmptyArray0); diff --git a/turbo-ext/src/AccessoryUppercaseStringType.cpp b/turbo-ext/src/AccessoryUppercaseStringType.cpp index 01d57f26a83..8feeae369ee 100644 --- a/turbo-ext/src/AccessoryUppercaseStringType.cpp +++ b/turbo-ext/src/AccessoryUppercaseStringType.cpp @@ -195,7 +195,7 @@ class AccessoryUppercaseStringType static zv::Val generalize() { return pt_type_new_string_type(); } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() { return pt_type_new_float_or_int_benevolent_union(); } + static zv::Val arithmeticPower() { return pt_type_new_float_or_int_benevolent_union(); } /* new IdentifierTypeNode('uppercase-string') */ static zv::Val toPhpDocNode() { return pt_type_new_identifier_type_node(PT_LC("uppercase-string")); } @@ -475,9 +475,9 @@ PT_MINIT_REGISTRATION(pt_register_accessory_uppercase_string_type) cls.method(sigs::traverseSimultaneously, aucsThis2); cls.method(sigs::generalize, aucsString1); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(AccessoryUppercaseStringType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(AccessoryUppercaseStringType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, aucsEmptyArray0); diff --git a/turbo-ext/src/BooleanType.cpp b/turbo-ext/src/BooleanType.cpp index 6902b6461ae..2399f19f1a5 100644 --- a/turbo-ext/src/BooleanType.cpp +++ b/turbo-ext/src/BooleanType.cpp @@ -143,8 +143,9 @@ class BooleanType static zv::Val getFiniteTypes() { return bothConstants(); } /* ExponentiateHelper::exponentiate($this, $exponent) */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; zv::Args args{self, exponent}; return pt_type_call_static(PT_CLASS_EXPONENTIATE_HELPER, PT_LC("exponentiate"), 2, args); } @@ -317,7 +318,7 @@ PT_MINIT_REGISTRATION(pt_register_boolean_type) cls.method<&BooleanType::getFiniteTypes>(sigs::getFiniteTypes); - cls.method<&BooleanType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&BooleanType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method<&BooleanType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/CallableType.cpp b/turbo-ext/src/CallableType.cpp index 69a84405ff2..ad6439a153a 100644 --- a/turbo-ext/src/CallableType.cpp +++ b/turbo-ext/src/CallableType.cpp @@ -1203,8 +1203,8 @@ PT_MINIT_REGISTRATION(pt_register_callable_type) ZEND_PARSE_PARAMETERS_NONE(); ctReturnSlot(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_THIS.isCommonCallableSlot()); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); }); cls.method(sigs::getFiniteTypes, ctEmptyArray0); diff --git a/turbo-ext/src/ClosureType.cpp b/turbo-ext/src/ClosureType.cpp index 4fe477174e3..a69cb859497 100644 --- a/turbo-ext/src/ClosureType.cpp +++ b/turbo-ext/src/ClosureType.cpp @@ -1285,8 +1285,8 @@ PT_MINIT_REGISTRATION(pt_register_closure_type) RETURN_COPY_VALUE(&result); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); }); cls.method(sigs::getFiniteTypes, cltEmptyArray0); diff --git a/turbo-ext/src/FloatType.cpp b/turbo-ext/src/FloatType.cpp index 4a098dd9868..31d6975ff2b 100644 --- a/turbo-ext/src/FloatType.cpp +++ b/turbo-ext/src/FloatType.cpp @@ -178,8 +178,9 @@ class FloatType zv::Val traverseSimultaneously() const { return thisValue(); } /* ExponentiateHelper::exponentiate($this, $exponent) */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; zv::Args args{self, exponent}; return pt_type_call_static(PT_CLASS_EXPONENTIATE_HELPER, PT_LC("exponentiate"), 2, args); } @@ -427,7 +428,7 @@ PT_MINIT_REGISTRATION(pt_register_float_type) PT_RETURN_VAL(PT_THIS.traverseSimultaneously()); }); - cls.method<&FloatType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&FloatType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method<&FloatType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/HasMethodType.cpp b/turbo-ext/src/HasMethodType.cpp index aa1c3e53c0b..ce04732364e 100644 --- a/turbo-ext/src/HasMethodType.cpp +++ b/turbo-ext/src/HasMethodType.cpp @@ -310,9 +310,9 @@ static void ZEND_FASTCALL hmtError0(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } -static void ZEND_FASTCALL hmtError1(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL hmtError2(INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); } @@ -445,7 +445,7 @@ PT_MINIT_REGISTRATION(pt_register_has_method_type) cls.op(PT_OP_GET_ENUM_CASE_OBJECT, PT_OP_LAMBDA { return zv::Val::null(); }); cls.method("traverse", reg::Public, 1, { reg::callableArg("cb") }, pt_type_identity_traverse_handler(), &ptret::type); cls.method(sigs::traverseSimultaneously, hmtThis2); - cls.method(sigs::exponentiate, hmtError1); + cls.method(sigs::arithmeticPower, hmtError2); cls.method(sigs::getFiniteTypes, hmtEmptyArray0); cls.method(sigs::getDefaultBaseType, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/HasOffsetType.cpp b/turbo-ext/src/HasOffsetType.cpp index 09c786abdc5..b544a1a5d13 100644 --- a/turbo-ext/src/HasOffsetType.cpp +++ b/turbo-ext/src/HasOffsetType.cpp @@ -465,9 +465,9 @@ static void ZEND_FASTCALL hotError0(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } -static void ZEND_FASTCALL hotError1(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL hotError2(INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); } @@ -688,7 +688,7 @@ PT_MINIT_REGISTRATION(pt_register_has_offset_type) cls.method("traverse", reg::Public, 1, { reg::callableArg("cb") }, pt_type_identity_traverse_handler(), &ptret::type); cls.op(PT_OP_TRAVERSE, PT_OP_LAMBDA { return pt_op_traverse_identity(self); }); cls.method(sigs::traverseSimultaneously, hotThis2); - cls.method(sigs::exponentiate, hotError1); + cls.method(sigs::arithmeticPower, hotError2); cls.method(sigs::getFiniteTypes, hotEmptyArray0); cls.method<&HasOffsetType::getDefaultBaseType>(sigs::getDefaultBaseType); diff --git a/turbo-ext/src/HasOffsetValueType.cpp b/turbo-ext/src/HasOffsetValueType.cpp index 64f6068cbf8..699ceaae626 100644 --- a/turbo-ext/src/HasOffsetValueType.cpp +++ b/turbo-ext/src/HasOffsetValueType.cpp @@ -775,9 +775,9 @@ static void ZEND_FASTCALL hovtError0(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } -static void ZEND_FASTCALL hovtError1(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL hovtError2(INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); } @@ -1042,7 +1042,7 @@ PT_MINIT_REGISTRATION(pt_register_has_offset_value_type) PT_RETURN_VAL(PT_THIS.traverseSimultaneously(right, &fci, &fcc)); }); - cls.method(sigs::exponentiate, hovtError1); + cls.method(sigs::arithmeticPower, hovtError2); cls.method(sigs::getFiniteTypes, hovtEmptyArray0); cls.method<&HasOffsetValueType::getDefaultBaseType>(sigs::getDefaultBaseType); diff --git a/turbo-ext/src/HasPropertyType.cpp b/turbo-ext/src/HasPropertyType.cpp index 72e6ee7a764..79451f12b01 100644 --- a/turbo-ext/src/HasPropertyType.cpp +++ b/turbo-ext/src/HasPropertyType.cpp @@ -264,9 +264,9 @@ static void ZEND_FASTCALL hptError0(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } -static void ZEND_FASTCALL hptError1(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL hptError2(INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); } @@ -389,7 +389,7 @@ PT_MINIT_REGISTRATION(pt_register_has_property_type) cls.op(PT_OP_GET_ENUM_CASE_OBJECT, PT_OP_LAMBDA { return zv::Val::null(); }); cls.method("traverse", reg::Public, 1, { reg::callableArg("cb") }, pt_type_identity_traverse_handler(), &ptret::type); cls.method(sigs::traverseSimultaneously, hptThis2); - cls.method(sigs::exponentiate, hptError1); + cls.method(sigs::arithmeticPower, hptError2); cls.method(sigs::getFiniteTypes, hptEmptyArray0); cls.method(sigs::getDefaultBaseType, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h new file mode 100644 index 00000000000..e964097fa0e --- /dev/null +++ b/turbo-ext/src/IetrSupport.h @@ -0,0 +1,646 @@ +/* + * The Type helpers shared by InitializerExprTypeResolver.cpp and + * OperatorTypeHelper.cpp: calling Type methods, constructing the native + * types, and the PHP value arithmetic of the twins' operators on the + * engine's own operator functions. + */ + +#ifndef PT_IETR_SUPPORT_H +#define PT_IETR_SUPPORT_H + +#include "support.h" +#include "zv.h" +#include "TypeTraits.h" +#include "TypeOps.h" + +#include +#include + +namespace phpstanturbo::ietr { + +/* InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT and + * OperatorTypeHelper's private IS_* codes */ +constexpr zend_long PT_IETR_CALCULATE_SCALARS_LIMIT = PT_INITIALIZER_EXPR_TYPE_RESOLVER_CALCULATE_SCALARS_LIMIT; +constexpr zend_long PT_IETR_IS_SCALAR_TYPE = 1; +constexpr zend_long PT_IETR_IS_UNKNOWN = 2; + +#define IETR_VAL(name, expr) \ + zv::Val name = (expr); \ + if (UNEXPECTED(name.isUndef())) return zv::Val() + +#define IETR_TRI(name, expr) \ + zend_long name = (expr); \ + if (UNEXPECTED(name < 0)) return zv::Val() + +/* the binary operators whose type the resolver and the helper compute */ +enum IetrBinaryKind : uint8_t +{ + IETR_PLUS, + IETR_MINUS, + IETR_MUL, + IETR_DIV, + IETR_MOD, + IETR_POW, + IETR_SHIFT_LEFT, + IETR_SHIFT_RIGHT, + IETR_BITWISE_AND, + IETR_BITWISE_OR, + IETR_BITWISE_XOR, + IETR_OTHER, +}; + +/* {{{ Type method calls */ + +/* a Type method the resolver calls: its name as written (the engine's Error + * for a non-object receiver names it), lowercased at compile time for the + * by-name lookup, and its TypeOps id when it has one */ +struct Method +{ + const char *name; + char lc[48]; + uint8_t len; + pt_type_op_id op; + + constexpr Method(const char *n, pt_type_op_id o = PT_OP_COUNT) : name(n), lc{}, len(0), op(o) + { + while (n[len] != '\0') { + char c = n[len]; + lc[len] = (c >= 'A' && c <= 'Z') ? (char) (c + ('a' - 'A')) : c; + len++; + } + } +}; + +inline constexpr Method mToNumber{"toNumber"}; +inline constexpr Method mToString{"toString"}; +inline constexpr Method mToInteger{"toInteger"}; +inline constexpr Method mToBoolean{"toBoolean"}; +inline constexpr Method mToFloat{"toFloat"}; +inline constexpr Method mToArray{"toArray"}; +inline constexpr Method mToBitwiseNotType{"toBitwiseNotType"}; +inline constexpr Method mToBooleanType{"toBooleanType"}; +inline constexpr Method mToClassConstantType{"toClassConstantType"}; +inline constexpr Method mGetConstantScalarTypes{"getConstantScalarTypes"}; +inline constexpr Method mGetConstantScalarValues{"getConstantScalarValues", PT_OP_GET_CONSTANT_SCALAR_VALUES}; +inline constexpr Method mGetFiniteTypes{"getFiniteTypes"}; +inline constexpr Method mGetConstantStrings{"getConstantStrings"}; +inline constexpr Method mGetConstantArrays{"getConstantArrays", PT_OP_GET_CONSTANT_ARRAYS}; +inline constexpr Method mIsInteger{"isInteger", PT_OP_IS_INTEGER}; +inline constexpr Method mIsString{"isString", PT_OP_IS_STRING}; +inline constexpr Method mIsFloat{"isFloat", PT_OP_IS_FLOAT}; +inline constexpr Method mIsNull{"isNull", PT_OP_IS_NULL}; +inline constexpr Method mIsArray{"isArray", PT_OP_IS_ARRAY}; +inline constexpr Method mIsList{"isList", PT_OP_IS_LIST}; +inline constexpr Method mIsVoid{"isVoid", PT_OP_IS_VOID}; +inline constexpr Method mIsObject{"isObject"}; +inline constexpr Method mIsEnum{"isEnum"}; +inline constexpr Method mIsTrue{"isTrue"}; +inline constexpr Method mIsFalse{"isFalse"}; +inline constexpr Method mIsConstantArray{"isConstantArray", PT_OP_IS_CONSTANT_ARRAY}; +inline constexpr Method mIsConstantScalarValue{"isConstantScalarValue", PT_OP_IS_CONSTANT_SCALAR_VALUE}; +inline constexpr Method mIsClassString{"isClassString"}; +inline constexpr Method mIsIterableAtLeastOnce{"isIterableAtLeastOnce", PT_OP_IS_ITERABLE_AT_LEAST_ONCE}; +inline constexpr Method mIsNonEmptyString{"isNonEmptyString"}; +inline constexpr Method mIsNonFalsyString{"isNonFalsyString"}; +inline constexpr Method mIsLiteralString{"isLiteralString"}; +inline constexpr Method mIsLowercaseString{"isLowercaseString"}; +inline constexpr Method mIsUppercaseString{"isUppercaseString"}; +inline constexpr Method mIsNumericString{"isNumericString"}; +inline constexpr Method mIsUnsealed{"isUnsealed", PT_OP_IS_UNSEALED}; +inline constexpr Method mGetUnsealedTypes{"getUnsealedTypes"}; +inline constexpr Method mGetIterableKeyType{"getIterableKeyType", PT_OP_GET_ITERABLE_KEY_TYPE}; +inline constexpr Method mGetIterableValueType{"getIterableValueType", PT_OP_GET_ITERABLE_VALUE_TYPE}; +inline constexpr Method mIsSuperTypeOf{"isSuperTypeOf", PT_OP_IS_SUPER_TYPE_OF}; +inline constexpr Method mEquals{"equals", PT_OP_EQUALS}; +inline constexpr Method mGetValue{"getValue", PT_OP_GET_VALUE}; +inline constexpr Method mGetTypes{"getTypes", PT_OP_GET_TYPES}; +inline constexpr Method mGetKeyTypes{"getKeyTypes", PT_OP_GET_KEY_TYPES}; +inline constexpr Method mGetValueTypes{"getValueTypes", PT_OP_GET_VALUE_TYPES}; +inline constexpr Method mIsOptionalKey{"isOptionalKey"}; +inline constexpr Method mHasOffsetValueType{"hasOffsetValueType", PT_OP_HAS_OFFSET_VALUE_TYPE}; +inline constexpr Method mGetOffsetValueType{"getOffsetValueType", PT_OP_GET_OFFSET_VALUE_TYPE}; +inline constexpr Method mAppend{"append"}; +inline constexpr Method mLooseCompare{"looseCompare"}; +inline constexpr Method mGetReasons{"getReasons"}; +inline constexpr Method mIsSmallerThan{"isSmallerThan"}; +inline constexpr Method mIsSmallerThanOrEqual{"isSmallerThanOrEqual"}; +inline constexpr Method mGetValueType{"getValueType"}; +inline constexpr Method mGetParentClass{"getParentClass", PT_OP_GET_PARENT_CLASS}; +inline constexpr Method mGetName{"getName", PT_OP_GET_NAME}; +inline constexpr Method mIsFinal{"isFinal", PT_OP_IS_FINAL}; +inline constexpr Method mHasConstant{"hasConstant"}; +inline constexpr Method mHasEnumCase{"hasEnumCase"}; +inline constexpr Method mGetNativeReflection{"getNativeReflection"}; +inline constexpr Method mGetReflectionConstant{"getReflectionConstant"}; +inline constexpr Method mGetDeclaringClass{"getDeclaringClass"}; +inline constexpr Method mGetFileName{"getFileName", PT_OP_GET_FILE_NAME}; +inline constexpr Method mGetValueExpression{"getValueExpression"}; +inline constexpr Method mGetType{"getType", PT_OP_GET_TYPE}; +inline constexpr Method mGetConstantPhpDocType{"getConstantPhpDocType"}; +inline constexpr Method mGetConstant{"getConstant"}; +inline constexpr Method mHasPhpDocType{"hasPhpDocType"}; +inline constexpr Method mHasNativeType{"hasNativeType"}; +inline constexpr Method mGetValueExpr{"getValueExpr"}; +inline constexpr Method mGetNativeType{"getNativeType"}; +inline constexpr Method mGetVariants{"getVariants"}; +inline constexpr Method mGetReturnType{"getReturnType"}; +inline constexpr Method mGetNativeReturnType{"getNativeReturnType"}; +inline constexpr Method mGetTemplateTypeMap{"getTemplateTypeMap", PT_OP_GET_TEMPLATE_TYPE_MAP}; +inline constexpr Method mGetBound{"getBound"}; +inline constexpr Method mGetDefault{"getDefault"}; +inline constexpr Method mGetVariance{"getVariance"}; +inline constexpr Method mGetThrowPoints{"getThrowPoints"}; +inline constexpr Method mGetImpurePoints{"getImpurePoints"}; +inline constexpr Method mAcceptsNamedArguments{"acceptsNamedArguments"}; +inline constexpr Method mMustUseReturnValue{"mustUseReturnValue"}; +inline constexpr Method mIsStaticClosure{"isStaticClosure"}; +inline constexpr Method mGetThrowType{"getThrowType"}; +inline constexpr Method mGetParameters{"getParameters"}; +inline constexpr Method mGetAsserts{"getAsserts"}; +inline constexpr Method mIsVariadic{"isVariadic", PT_OP_IS_VARIADIC}; +inline constexpr Method mGetResolvedTemplateTypeMap{"getResolvedTemplateTypeMap"}; +inline constexpr Method mGetCallSiteVarianceMap{"getCallSiteVarianceMap", PT_OP_GET_CALL_SITE_VARIANCE_MAP}; +inline constexpr Method mHasMethod{"hasMethod", PT_OP_HAS_METHOD}; +inline constexpr Method mGetMethod{"getMethod", PT_OP_GET_METHOD}; +inline constexpr Method mIsStatic{"isStatic"}; +inline constexpr Method mGetStaticObjectType{"getStaticObjectType"}; +inline constexpr Method mChangeBaseClass{"changeBaseClass", PT_OP_CHANGE_BASE_CLASS}; +inline constexpr Method mGetClassReflection{"getClassReflection", PT_OP_GET_CLASS_REFLECTION}; +inline constexpr Method mGetObjectClassReflections{"getObjectClassReflections", PT_OP_GET_OBJECT_CLASS_REFLECTIONS}; +inline constexpr Method mGetAncestorWithClassName{"getAncestorWithClassName", PT_OP_GET_ANCESTOR_WITH_CLASS_NAME}; +inline constexpr Method mGetClassName{"getClassName"}; +inline constexpr Method mGetClassStringObjectType{"getClassStringObjectType"}; +inline constexpr Method mGetObjectClassNames{"getObjectClassNames", PT_OP_GET_OBJECT_CLASS_NAMES}; +inline constexpr Method mHasInstanceProperty{"hasInstanceProperty", PT_OP_HAS_INSTANCE_PROPERTY}; +inline constexpr Method mGetInstanceProperty{"getInstanceProperty", PT_OP_GET_INSTANCE_PROPERTY}; +inline constexpr Method mGetReadableType{"getReadableType"}; +inline constexpr Method mGetExprType{"getExprType"}; +inline constexpr Method mGeneralize{"generalize"}; +inline constexpr Method mGetStartLine{"getStartLine"}; + +/* the engine's Error for a method call on a non-object */ +inline void throwCallOnNonObject(const Method &m, zval *receiver) +{ + zend_throw_error(NULL, "Call to a member function %s() on %s", m.name, zend_zval_value_name(receiver)); +} + +/* $receiver->method(...$argv); UNDEF = pending exception */ +inline zv::Val callOn(zval *receiver, const Method &m, uint32_t argc = 0, zval *argv = NULL) +{ + if (UNEXPECTED(Z_TYPE_P(receiver) != IS_OBJECT)) { + throwCallOnNonObject(m, receiver); + return zv::Val(); + } + if (m.op != PT_OP_COUNT) return pt_type_op(Z_OBJ_P(receiver), m.op, argc, argv); + return pt_type_call(Z_OBJ_P(receiver), m.lc, m.len, argc, argv); +} + +/* the PT_TRI_* value of a TrinaryLogic / result object's ->yes()/->no() + * family (a TrinaryLogic or an IsSuperTypeOfResult / AcceptsResult); -1 = + * pending exception */ +inline zend_long trinaryOf(zval *value) +{ + if (EXPECTED(Z_TYPE_P(value) == IS_OBJECT)) { + zend_class_entry *ce = Z_OBJCE_P(value); + if (EXPECTED(ce == pt_ce_trinary)) return pt_trinary_value(Z_OBJ_P(value)); + if (ce == pt_ce_is_super_type_of_result || ce == pt_ce_accepts_result) return pt_type_result_trinary(value); + zv::Val yes = pt_type_call(Z_OBJ_P(value), PT_LC("yes"), 0, NULL); + if (UNEXPECTED(yes.isUndef())) return -1; + if (zend_is_true(yes.raw())) return PT_TRI_YES; + zv::Val no = pt_type_call(Z_OBJ_P(value), PT_LC("no"), 0, NULL); + if (UNEXPECTED(no.isUndef())) return -1; + return zend_is_true(no.raw()) ? PT_TRI_NO : PT_TRI_MAYBE; + } + zend_throw_error(NULL, "Call to a member function yes() on %s", zend_zval_value_name(value)); + return -1; +} + +/* $receiver->method(...$argv) of a method returning TrinaryLogic (or a + * result object): the PT_TRI_* value, -1 = pending exception */ +inline zend_long triOn(zval *receiver, const Method &m, uint32_t argc = 0, zval *argv = NULL) +{ + zv::Val result = callOn(receiver, m, argc, argv); + if (UNEXPECTED(result.isUndef())) return -1; + return trinaryOf(result.raw()); +} + +/* $value instanceof */ +inline bool isA(zval *value, zend_class_entry *ce) +{ + return Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce); +} + +/* $value instanceof ; -1 = pending exception */ +inline int isAClass(zval *value, int classIdx) +{ + zend_class_entry *ce = pt_class(classIdx); + if (UNEXPECTED(ce == NULL)) return -1; + return Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce) ? 1 : 0; +} + +/* }}} */ + +/* {{{ Type construction */ + +inline zv::Val adopted(bool ok, zval &value) +{ + if (UNEXPECTED(!ok)) return zv::Val(); + return zv::Val::adopt(value); +} + +inline zv::Val newErrorType() +{ + zval z; + return adopted(pt_error_type_new(&z), z); +} + +inline zv::Val newMixedType(bool isExplicitMixed = false) +{ + zval z; + return adopted(pt_mixed_type_new(&z, isExplicitMixed), z); +} + +inline zv::Val newNeverType() +{ + zval z; + return adopted(pt_never_type_new(&z), z); +} + +inline zv::Val newIntegerType() +{ + zval z; + return adopted(pt_integer_type_new(&z), z); +} + +inline zv::Val newFloatType() +{ + zval z; + return adopted(pt_float_type_new(&z), z); +} + +inline zv::Val newStringType() +{ + zval z; + return adopted(pt_string_type_new(&z), z); +} + +inline zv::Val newNullType() +{ + zval z; + return adopted(pt_null_type_new(&z), z); +} + +inline zv::Val newBooleanType() +{ + zval z; + return adopted(pt_boolean_type_new(&z), z); +} + +inline zv::Val newConstantBooleanType(bool value) +{ + zval z; + return adopted(pt_constant_boolean_type_new(&z, value), z); +} + +inline zv::Val newConstantIntegerType(zend_long value) +{ + zval z; + return adopted(pt_constant_integer_type_new(&z, value), z); +} + +inline zv::Val newConstantFloatType(double value) +{ + zval z; + return adopted(pt_constant_float_type_new(&z, value), z); +} + +inline zv::Val newConstantStringType(zend_string *value, bool isClassString = false) +{ + zval z; + return adopted(pt_constant_string_type_new(&z, value, isClassString), z); +} + +inline zv::Val newClassStringType() +{ + zval z; + return adopted(pt_class_string_type_new(&z), z); +} + +inline zv::Val newObjectType(zend_string *className) +{ + zval z; + return adopted(pt_object_type_new(&z, className), z); +} + +inline zv::Val newObjectTypeLiteral(const char *className, size_t len) +{ + zend_string *name = zend_string_init(className, len, 0); + zv::Val type = newObjectType(name); + zend_string_release(name); + return type; +} + +inline zv::Val newArrayType(zval *keyType, zval *itemType) +{ + zval z; + return adopted(pt_array_type_new(&z, keyType, itemType), z); +} + +inline zv::Val newUnionType(zv::Arr &types) +{ + zval z; + return adopted(pt_union_type_new(&z, types.raw()), z); +} + +inline zv::Val newBenevolentUnionType(zv::Arr &types) +{ + zval z; + return adopted(pt_benevolent_union_type_new(&z, types.raw()), z); +} + +inline zv::Val newIntersectionType(zv::Arr &types) +{ + zval z; + return adopted(pt_intersection_type_new(&z, types.raw()), z); +} + +/* new BenevolentUnionType([new IntegerType(), new FloatType()]) (the order + * the twin spells: first, second) */ +inline zv::Val newIntFloatUnion(bool benevolent, bool floatFirst = false) +{ + IETR_VAL(integerType, newIntegerType()); + IETR_VAL(floatType, newFloatType()); + zv::Arr types = zv::Arr::create(2); + if (floatFirst) { + types.push(std::move(floatType)); + types.push(std::move(integerType)); + } else { + types.push(std::move(integerType)); + types.push(std::move(floatType)); + } + return benevolent ? newBenevolentUnionType(types) : newUnionType(types); +} + +/* new TypeResult($type, []) */ +inline zv::Val newTypeResult(zv::Val type) +{ + if (UNEXPECTED(type.isUndef())) return zv::Val(); + zval reasons; + ZVAL_EMPTY_ARRAY(&reasons); + return pt_type_result_new(type.raw(), &reasons); +} + +/* TypeCombinator::union(...$types) of a list the resolver built */ +inline zv::Val unionOf(zv::Arr &types) +{ + HashTable *ht = types.table(); + uint32_t count = zend_hash_num_elements(ht); + if (count == 0) return pt_type_combinator_union(0, NULL); + if (EXPECTED(HT_IS_PACKED(ht) && HT_IS_WITHOUT_HOLES(ht))) return pt_type_combinator_union(count, ht->arPacked); + return pt_type_combinator_call_spread(PT_LC("union"), ht); +} + +/* TypeCombinator::union($a, $b) */ +inline zv::Val union2(zval *a, zval *b) +{ + zv::Args argv{a, b}; + return pt_type_combinator_union(2, argv); +} + +/* ConstantTypeHelper::getTypeFromValue($value) */ +inline zv::Val typeFromValue(zval *value) +{ + return pt_constant_type_helper_get_type_from_value(value); +} + +/* IntegerRangeType::fromInterval($min, $max) of int|null zvals; a float (the + * twin's arithmetic overflowed) raises the TypeError of the twin's + * strict-types call */ +inline zv::Val fromIntervalZv(zval *min, zval *max) +{ + bool minOk = Z_TYPE_P(min) == IS_LONG || Z_TYPE_P(min) == IS_NULL; + bool maxOk = Z_TYPE_P(max) == IS_LONG || Z_TYPE_P(max) == IS_NULL; + if (EXPECTED(minOk && maxOk)) return pt_integer_range_from_interval(NullableLong::from(min), NullableLong::from(max), 0); + zend_type_error("PHPStan\\Type\\IntegerRangeType::fromInterval(): Argument #%d ($%s) must be of type ?int, %s given", minOk ? 2 : 1, minOk ? "max" : "min", zend_zval_value_name(minOk ? max : min)); + return zv::Val(); +} + +inline zv::Val fromInterval(NullableLong min, NullableLong max) +{ + return pt_integer_range_from_interval(min, max, 0); +} + +/* TypeUtils::toBenevolentUnion($type) */ +inline zv::Val toBenevolentUnion(zval *type) +{ + return pt_type_call_static_ce(pt_ce_type_utils, PT_LC("tobenevolentunion"), 1, type); +} + +/* }}} */ + +/* {{{ PHP value arithmetic (the twin's operators, on the engine's functions) */ + +typedef zend_result (ZEND_FASTCALL *pt_ietr_binary_op)(zval *result, zval *op1, zval *op2); + +/* $a $b; UNDEF = pending exception */ +inline zv::Val phpOp(pt_ietr_binary_op fn, zval *a, zval *b) +{ + zval result; + ZVAL_UNDEF(&result); + if (UNEXPECTED(fn(&result, a, b) != SUCCESS || EG(exception) != NULL)) { + zval_ptr_dtor(&result); + return zv::Val(); + } + return zv::Val::adopt(result); +} + +/* an internal function of the engine's function table called directly + * (the function the twin calls: min(), max(), dirname()), its lookup cached + * in `cache`; UNDEF = pending exception */ +inline zv::Val callInternalFunction(zend_function *&cache, const char *name, size_t len, uint32_t argc, zval *argv) +{ + if (UNEXPECTED(cache == NULL)) { + cache = (zend_function *) zend_hash_str_find_ptr(CG(function_table), name, len); + if (UNEXPECTED(cache == NULL)) { + zend_throw_error(NULL, "Call to undefined function %s()", name); + return zv::Val(); + } + } + zval result; + ZVAL_UNDEF(&result); + zend_call_known_function(cache, NULL, NULL, &result, argc, argv, NULL); + if (UNEXPECTED(EG(exception) != NULL)) { + zval_ptr_dtor(&result); + return zv::Val(); + } + return zv::Val::adopt(result); +} + +/* min(...$argv) / max(...$argv) */ +inline zv::Val phpMinMax(bool max, uint32_t argc, zval *argv) +{ + static zend_function *minFn = NULL; + static zend_function *maxFn = NULL; + return max ? callInternalFunction(maxFn, PT_LC("max"), argc, argv) : callInternalFunction(minFn, PT_LC("min"), argc, argv); +} + +/* dirname($path) */ +inline zv::Val phpDirname(zval *path) +{ + static zend_function *dirnameFn = NULL; + return callInternalFunction(dirnameFn, PT_LC("dirname"), 1, path); +} + +/* $value === 0 || $value === 0.0 — in_array($value, [0, 0.0], true) */ +inline bool isZeroNumber(zval *value) +{ + return (Z_TYPE_P(value) == IS_LONG && Z_LVAL_P(value) == 0) || (Z_TYPE_P(value) == IS_DOUBLE && Z_DVAL_P(value) == 0.0); +} + +/* $a === $b for scalars */ +inline bool identical(zval *a, zval *b) +{ + return zend_is_identical(a, b); +} + +/* $a > $b / $a < $b (zend_compare, as the VM's IS_SMALLER) */ +inline bool greaterThan(zval *a, zval *b) +{ + return zend_compare(b, a) < 0; +} + +inline bool lessThan(zval *a, zval *b) +{ + return zend_compare(a, b) < 0; +} + +/* }}} */ + +/* {{{ value helpers */ + +/* count() / foreach over a Type method's array result: the engine's + * TypeError / warning for anything else; false = pending exception */ +inline bool requireArray(zval *value) +{ + if (EXPECTED(Z_TYPE_P(value) == IS_ARRAY)) return true; + zend_type_error("count(): Argument #1 ($value) must be of type Countable|array, %s given", zend_zval_value_name(value)); + return false; +} + +inline bool requireCountable(zval *value) +{ + return requireArray(value); +} + +/* $array[$index] of a list the twin indexes ($leftConstantArrays[0], + * $existingUnsealed[0]); the twin reads only indexes it knows exist, so a + * miss is a broken collaborator: ShouldNotHappenException */ +inline zval *arrayIndex(zval *array, zend_ulong index) +{ + if (EXPECTED(Z_TYPE_P(array) == IS_ARRAY)) { + zval *value = zend_hash_index_find(Z_ARRVAL_P(array), index); + if (EXPECTED(value != NULL)) { + ZVAL_DEREF(value); + return value; + } + } + pt_throw_should_not_happen(); + return NULL; +} + +/* the foreach key of an entry as a PHP value (borrowed string) */ +inline void entryKey(const zv::ArrayEntry &entry, zval &key) +{ + zend_string *stringKey = entry.stringKeyOrNull(); + if (stringKey != NULL) { + ZVAL_STR(&key, stringKey); + } else { + ZVAL_LONG(&key, (zend_long) entry.indexKey()); + } +} + +/* $constantString->getValue() */ +inline zv::Val constantStringValue(zval *constantString) +{ + if (EXPECTED(isA(constantString, pt_ce_constant_string_type))) return pt_constant_string_get_value(Z_OBJ_P(constantString)); + return callOn(constantString, mGetValue); +} + +/* $constantString->getValue() === '' of a ConstantStringType; -1 = + * pending exception */ +inline int constantStringValueIsEmpty(zval *constantString) +{ + zv::Val value = constantStringValue(constantString); + if (UNEXPECTED(value.isUndef())) return -1; + return Z_TYPE_P(value.raw()) == IS_STRING && ZSTR_LEN(Z_STR_P(value.raw())) == 0 ? 1 : 0; +} + +/* $type instanceof ConstantStringType && $type->getValue() === ''; + * -1 = pending exception */ +inline int isEmptyConstantString(zval *type) +{ + if (!isA(type, pt_ce_constant_string_type)) return 0; + return constantStringValueIsEmpty(type); +} + +/* Strings::match($value, '#^\d+$#') !== null: ASCII digits, a final + * newline allowed before the end ($ without D) */ +inline bool isDecimalDigits(zend_string *value) +{ + size_t length = ZSTR_LEN(value); + if (length > 0 && ZSTR_VAL(value)[length - 1] == '\n') length--; + if (length == 0) return false; + for (size_t i = 0; i < length; i++) { + char c = ZSTR_VAL(value)[i]; + if (c < '0' || c > '9') return false; + } + return true; +} + +/* $type instanceof ConstantIntegerType && $type->getValue() === 0; -1 = + * pending exception */ +inline int isConstantIntegerZero(zval *type) +{ + if (!isA(type, pt_ce_constant_integer_type)) return 0; + zend_long value; + if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(type), value))) return -1; + return value == 0 ? 1 : 0; +} + +/* $result = $a $b for scalar operands (no release needed); false = + * pending exception */ +inline bool scalarOp(pt_ietr_binary_op fn, zval *a, zval *b, zval *result) +{ + ZVAL_UNDEF(result); + if (UNEXPECTED(fn(result, a, b) != SUCCESS || EG(exception) != NULL)) { + zval_ptr_dtor(result); + ZVAL_UNDEF(result); + return false; + } + return true; +} + +/* `$a === 0 || $b === 0 ? 0 : ($a ?? $aDefault) * ($b ?? $bDefault)` */ +inline bool mulOrZero(zval *a, zval *b, zval *aDefault, zval *bDefault, zval *result) +{ + if ((Z_TYPE_P(a) == IS_LONG && Z_LVAL_P(a) == 0) || (Z_TYPE_P(b) == IS_LONG && Z_LVAL_P(b) == 0)) { + ZVAL_LONG(result, 0); + return true; + } + return scalarOp(mul_function, Z_TYPE_P(a) != IS_NULL ? a : aDefault, Z_TYPE_P(b) != IS_NULL ? b : bDefault, result); +} + +inline void setNullableLong(zval *out, NullableLong value) +{ + if (value.isNull) { + ZVAL_NULL(out); + } else { + ZVAL_LONG(out, value.value); + } +} + +/* }}} */ + +} // namespace phpstanturbo::ietr + +#endif diff --git a/turbo-ext/src/InitializerExprTypeResolver.cpp b/turbo-ext/src/InitializerExprTypeResolver.cpp index 8bd42468bb0..0194a974abc 100644 --- a/turbo-ext/src/InitializerExprTypeResolver.cpp +++ b/turbo-ext/src/InitializerExprTypeResolver.cpp @@ -40,6 +40,7 @@ #include "generated/InitializerExprTypeResolver.h" #include "zv.h" +#include "IetrSupport.h" #include "TypeTraits.h" #include "TypeOps.h" #include "Engine.h" @@ -54,492 +55,12 @@ zend_class_entry *pt_ce_initializer_expr_type_resolver = nullptr; namespace slots = ptdecl::InitializerExprTypeResolver::slot; namespace sigs = ptdecl::InitializerExprTypeResolver::sig; +using namespace phpstanturbo::ietr; + namespace { -using phpstanturbo::NullableLong; using phpstanturbo::visitors::NodeProp; -/* the twin's CALCULATE_SCALARS_LIMIT and its private IS_* codes */ -constexpr zend_long PT_IETR_CALCULATE_SCALARS_LIMIT = PT_INITIALIZER_EXPR_TYPE_RESOLVER_CALCULATE_SCALARS_LIMIT; -constexpr zend_long PT_IETR_IS_SCALAR_TYPE = 1; -constexpr zend_long PT_IETR_IS_UNKNOWN = 2; - -#define IETR_VAL(name, expr) \ - zv::Val name = (expr); \ - if (UNEXPECTED(name.isUndef())) return zv::Val() - -#define IETR_TRI(name, expr) \ - zend_long name = (expr); \ - if (UNEXPECTED(name < 0)) return zv::Val() - -/* {{{ Type method calls */ - -/* a Type method the resolver calls: its name as written (the engine's Error - * for a non-object receiver names it), lowercased at compile time for the - * by-name lookup, and its TypeOps id when it has one */ -struct Method -{ - const char *name; - char lc[48]; - uint8_t len; - pt_type_op_id op; - - constexpr Method(const char *n, pt_type_op_id o = PT_OP_COUNT) : name(n), lc{}, len(0), op(o) - { - while (n[len] != '\0') { - char c = n[len]; - lc[len] = (c >= 'A' && c <= 'Z') ? (char) (c + ('a' - 'A')) : c; - len++; - } - } -}; - -constexpr Method mToNumber{"toNumber"}; -constexpr Method mToString{"toString"}; -constexpr Method mToInteger{"toInteger"}; -constexpr Method mToBoolean{"toBoolean"}; -constexpr Method mToFloat{"toFloat"}; -constexpr Method mToArray{"toArray"}; -constexpr Method mToBitwiseNotType{"toBitwiseNotType"}; -constexpr Method mToBooleanType{"toBooleanType"}; -constexpr Method mToClassConstantType{"toClassConstantType"}; -constexpr Method mGetConstantScalarTypes{"getConstantScalarTypes"}; -constexpr Method mGetConstantScalarValues{"getConstantScalarValues", PT_OP_GET_CONSTANT_SCALAR_VALUES}; -constexpr Method mGetFiniteTypes{"getFiniteTypes"}; -constexpr Method mGetConstantStrings{"getConstantStrings"}; -constexpr Method mGetConstantArrays{"getConstantArrays", PT_OP_GET_CONSTANT_ARRAYS}; -constexpr Method mIsInteger{"isInteger", PT_OP_IS_INTEGER}; -constexpr Method mIsString{"isString", PT_OP_IS_STRING}; -constexpr Method mIsFloat{"isFloat", PT_OP_IS_FLOAT}; -constexpr Method mIsNull{"isNull", PT_OP_IS_NULL}; -constexpr Method mIsArray{"isArray", PT_OP_IS_ARRAY}; -constexpr Method mIsList{"isList", PT_OP_IS_LIST}; -constexpr Method mIsVoid{"isVoid", PT_OP_IS_VOID}; -constexpr Method mIsObject{"isObject"}; -constexpr Method mIsEnum{"isEnum"}; -constexpr Method mIsTrue{"isTrue"}; -constexpr Method mIsFalse{"isFalse"}; -constexpr Method mIsConstantArray{"isConstantArray", PT_OP_IS_CONSTANT_ARRAY}; -constexpr Method mIsConstantScalarValue{"isConstantScalarValue", PT_OP_IS_CONSTANT_SCALAR_VALUE}; -constexpr Method mIsClassString{"isClassString"}; -constexpr Method mIsIterableAtLeastOnce{"isIterableAtLeastOnce", PT_OP_IS_ITERABLE_AT_LEAST_ONCE}; -constexpr Method mIsNonEmptyString{"isNonEmptyString"}; -constexpr Method mIsNonFalsyString{"isNonFalsyString"}; -constexpr Method mIsLiteralString{"isLiteralString"}; -constexpr Method mIsLowercaseString{"isLowercaseString"}; -constexpr Method mIsUppercaseString{"isUppercaseString"}; -constexpr Method mIsNumericString{"isNumericString"}; -constexpr Method mIsUnsealed{"isUnsealed", PT_OP_IS_UNSEALED}; -constexpr Method mGetUnsealedTypes{"getUnsealedTypes"}; -constexpr Method mGetIterableKeyType{"getIterableKeyType", PT_OP_GET_ITERABLE_KEY_TYPE}; -constexpr Method mGetIterableValueType{"getIterableValueType", PT_OP_GET_ITERABLE_VALUE_TYPE}; -constexpr Method mIsSuperTypeOf{"isSuperTypeOf", PT_OP_IS_SUPER_TYPE_OF}; -constexpr Method mEquals{"equals", PT_OP_EQUALS}; -constexpr Method mGetValue{"getValue", PT_OP_GET_VALUE}; -constexpr Method mGetTypes{"getTypes", PT_OP_GET_TYPES}; -constexpr Method mGetKeyTypes{"getKeyTypes", PT_OP_GET_KEY_TYPES}; -constexpr Method mGetValueTypes{"getValueTypes", PT_OP_GET_VALUE_TYPES}; -constexpr Method mIsOptionalKey{"isOptionalKey"}; -constexpr Method mHasOffsetValueType{"hasOffsetValueType", PT_OP_HAS_OFFSET_VALUE_TYPE}; -constexpr Method mGetOffsetValueType{"getOffsetValueType", PT_OP_GET_OFFSET_VALUE_TYPE}; -constexpr Method mAppend{"append"}; -constexpr Method mExponentiate{"exponentiate"}; -constexpr Method mLooseCompare{"looseCompare"}; -constexpr Method mGetReasons{"getReasons"}; -constexpr Method mIsSmallerThan{"isSmallerThan"}; -constexpr Method mIsSmallerThanOrEqual{"isSmallerThanOrEqual"}; -constexpr Method mGetValueType{"getValueType"}; -constexpr Method mGetParentClass{"getParentClass", PT_OP_GET_PARENT_CLASS}; -constexpr Method mGetName{"getName", PT_OP_GET_NAME}; -constexpr Method mIsFinal{"isFinal", PT_OP_IS_FINAL}; -constexpr Method mHasConstant{"hasConstant"}; -constexpr Method mHasEnumCase{"hasEnumCase"}; -constexpr Method mGetNativeReflection{"getNativeReflection"}; -constexpr Method mGetReflectionConstant{"getReflectionConstant"}; -constexpr Method mGetDeclaringClass{"getDeclaringClass"}; -constexpr Method mGetFileName{"getFileName", PT_OP_GET_FILE_NAME}; -constexpr Method mGetValueExpression{"getValueExpression"}; -constexpr Method mGetType{"getType", PT_OP_GET_TYPE}; -constexpr Method mGetConstantPhpDocType{"getConstantPhpDocType"}; -constexpr Method mGetConstant{"getConstant"}; -constexpr Method mHasPhpDocType{"hasPhpDocType"}; -constexpr Method mHasNativeType{"hasNativeType"}; -constexpr Method mGetValueExpr{"getValueExpr"}; -constexpr Method mGetNativeType{"getNativeType"}; -constexpr Method mGetVariants{"getVariants"}; -constexpr Method mGetReturnType{"getReturnType"}; -constexpr Method mGetNativeReturnType{"getNativeReturnType"}; -constexpr Method mGetTemplateTypeMap{"getTemplateTypeMap", PT_OP_GET_TEMPLATE_TYPE_MAP}; -constexpr Method mGetBound{"getBound"}; -constexpr Method mGetDefault{"getDefault"}; -constexpr Method mGetVariance{"getVariance"}; -constexpr Method mGetThrowPoints{"getThrowPoints"}; -constexpr Method mGetImpurePoints{"getImpurePoints"}; -constexpr Method mAcceptsNamedArguments{"acceptsNamedArguments"}; -constexpr Method mMustUseReturnValue{"mustUseReturnValue"}; -constexpr Method mIsStaticClosure{"isStaticClosure"}; -constexpr Method mGetThrowType{"getThrowType"}; -constexpr Method mGetParameters{"getParameters"}; -constexpr Method mGetAsserts{"getAsserts"}; -constexpr Method mIsVariadic{"isVariadic", PT_OP_IS_VARIADIC}; -constexpr Method mGetResolvedTemplateTypeMap{"getResolvedTemplateTypeMap"}; -constexpr Method mGetCallSiteVarianceMap{"getCallSiteVarianceMap", PT_OP_GET_CALL_SITE_VARIANCE_MAP}; -constexpr Method mHasMethod{"hasMethod", PT_OP_HAS_METHOD}; -constexpr Method mGetMethod{"getMethod", PT_OP_GET_METHOD}; -constexpr Method mIsStatic{"isStatic"}; -constexpr Method mGetStaticObjectType{"getStaticObjectType"}; -constexpr Method mChangeBaseClass{"changeBaseClass", PT_OP_CHANGE_BASE_CLASS}; -constexpr Method mGetClassReflection{"getClassReflection", PT_OP_GET_CLASS_REFLECTION}; -constexpr Method mGetObjectClassReflections{"getObjectClassReflections", PT_OP_GET_OBJECT_CLASS_REFLECTIONS}; -constexpr Method mGetAncestorWithClassName{"getAncestorWithClassName", PT_OP_GET_ANCESTOR_WITH_CLASS_NAME}; -constexpr Method mGetClassName{"getClassName"}; -constexpr Method mGetClassStringObjectType{"getClassStringObjectType"}; -constexpr Method mGetObjectClassNames{"getObjectClassNames", PT_OP_GET_OBJECT_CLASS_NAMES}; -constexpr Method mHasInstanceProperty{"hasInstanceProperty", PT_OP_HAS_INSTANCE_PROPERTY}; -constexpr Method mGetInstanceProperty{"getInstanceProperty", PT_OP_GET_INSTANCE_PROPERTY}; -constexpr Method mGetReadableType{"getReadableType"}; -constexpr Method mGetExprType{"getExprType"}; -constexpr Method mGeneralize{"generalize"}; -constexpr Method mGetStartLine{"getStartLine"}; - -/* the engine's Error for a method call on a non-object */ -inline void throwCallOnNonObject(const Method &m, zval *receiver) -{ - zend_throw_error(NULL, "Call to a member function %s() on %s", m.name, zend_zval_value_name(receiver)); -} - -/* $receiver->method(...$argv); UNDEF = pending exception */ -inline zv::Val callOn(zval *receiver, const Method &m, uint32_t argc = 0, zval *argv = NULL) -{ - if (UNEXPECTED(Z_TYPE_P(receiver) != IS_OBJECT)) { - throwCallOnNonObject(m, receiver); - return zv::Val(); - } - if (m.op != PT_OP_COUNT) return pt_type_op(Z_OBJ_P(receiver), m.op, argc, argv); - return pt_type_call(Z_OBJ_P(receiver), m.lc, m.len, argc, argv); -} - -/* the PT_TRI_* value of a TrinaryLogic / result object's ->yes()/->no() - * family (a TrinaryLogic or an IsSuperTypeOfResult / AcceptsResult); -1 = - * pending exception */ -inline zend_long trinaryOf(zval *value) -{ - if (EXPECTED(Z_TYPE_P(value) == IS_OBJECT)) { - zend_class_entry *ce = Z_OBJCE_P(value); - if (EXPECTED(ce == pt_ce_trinary)) return pt_trinary_value(Z_OBJ_P(value)); - if (ce == pt_ce_is_super_type_of_result || ce == pt_ce_accepts_result) return pt_type_result_trinary(value); - zv::Val yes = pt_type_call(Z_OBJ_P(value), PT_LC("yes"), 0, NULL); - if (UNEXPECTED(yes.isUndef())) return -1; - if (zend_is_true(yes.raw())) return PT_TRI_YES; - zv::Val no = pt_type_call(Z_OBJ_P(value), PT_LC("no"), 0, NULL); - if (UNEXPECTED(no.isUndef())) return -1; - return zend_is_true(no.raw()) ? PT_TRI_NO : PT_TRI_MAYBE; - } - zend_throw_error(NULL, "Call to a member function yes() on %s", zend_zval_value_name(value)); - return -1; -} - -/* $receiver->method(...$argv) of a method returning TrinaryLogic (or a - * result object): the PT_TRI_* value, -1 = pending exception */ -inline zend_long triOn(zval *receiver, const Method &m, uint32_t argc = 0, zval *argv = NULL) -{ - zv::Val result = callOn(receiver, m, argc, argv); - if (UNEXPECTED(result.isUndef())) return -1; - return trinaryOf(result.raw()); -} - -/* $value instanceof */ -inline bool isA(zval *value, zend_class_entry *ce) -{ - return Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce); -} - -/* $value instanceof ; -1 = pending exception */ -inline int isAClass(zval *value, int classIdx) -{ - zend_class_entry *ce = pt_class(classIdx); - if (UNEXPECTED(ce == NULL)) return -1; - return Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce) ? 1 : 0; -} - -/* }}} */ - -/* {{{ Type construction */ - -inline zv::Val adopted(bool ok, zval &value) -{ - if (UNEXPECTED(!ok)) return zv::Val(); - return zv::Val::adopt(value); -} - -zv::Val newErrorType() -{ - zval z; - return adopted(pt_error_type_new(&z), z); -} - -zv::Val newMixedType(bool isExplicitMixed = false) -{ - zval z; - return adopted(pt_mixed_type_new(&z, isExplicitMixed), z); -} - -zv::Val newNeverType() -{ - zval z; - return adopted(pt_never_type_new(&z), z); -} - -zv::Val newIntegerType() -{ - zval z; - return adopted(pt_integer_type_new(&z), z); -} - -zv::Val newFloatType() -{ - zval z; - return adopted(pt_float_type_new(&z), z); -} - -zv::Val newStringType() -{ - zval z; - return adopted(pt_string_type_new(&z), z); -} - -zv::Val newNullType() -{ - zval z; - return adopted(pt_null_type_new(&z), z); -} - -zv::Val newBooleanType() -{ - zval z; - return adopted(pt_boolean_type_new(&z), z); -} - -zv::Val newConstantBooleanType(bool value) -{ - zval z; - return adopted(pt_constant_boolean_type_new(&z, value), z); -} - -zv::Val newConstantIntegerType(zend_long value) -{ - zval z; - return adopted(pt_constant_integer_type_new(&z, value), z); -} - -zv::Val newConstantFloatType(double value) -{ - zval z; - return adopted(pt_constant_float_type_new(&z, value), z); -} - -zv::Val newConstantStringType(zend_string *value, bool isClassString = false) -{ - zval z; - return adopted(pt_constant_string_type_new(&z, value, isClassString), z); -} - -zv::Val newClassStringType() -{ - zval z; - return adopted(pt_class_string_type_new(&z), z); -} - -zv::Val newObjectType(zend_string *className) -{ - zval z; - return adopted(pt_object_type_new(&z, className), z); -} - -zv::Val newObjectTypeLiteral(const char *className, size_t len) -{ - zend_string *name = zend_string_init(className, len, 0); - zv::Val type = newObjectType(name); - zend_string_release(name); - return type; -} - -zv::Val newArrayType(zval *keyType, zval *itemType) -{ - zval z; - return adopted(pt_array_type_new(&z, keyType, itemType), z); -} - -zv::Val newUnionType(zv::Arr &types) -{ - zval z; - return adopted(pt_union_type_new(&z, types.raw()), z); -} - -zv::Val newBenevolentUnionType(zv::Arr &types) -{ - zval z; - return adopted(pt_benevolent_union_type_new(&z, types.raw()), z); -} - -zv::Val newIntersectionType(zv::Arr &types) -{ - zval z; - return adopted(pt_intersection_type_new(&z, types.raw()), z); -} - -/* new BenevolentUnionType([new IntegerType(), new FloatType()]) (the order - * the twin spells: first, second) */ -zv::Val newIntFloatUnion(bool benevolent, bool floatFirst = false) -{ - IETR_VAL(integerType, newIntegerType()); - IETR_VAL(floatType, newFloatType()); - zv::Arr types = zv::Arr::create(2); - if (floatFirst) { - types.push(std::move(floatType)); - types.push(std::move(integerType)); - } else { - types.push(std::move(integerType)); - types.push(std::move(floatType)); - } - return benevolent ? newBenevolentUnionType(types) : newUnionType(types); -} - -/* new TypeResult($type, []) */ -zv::Val newTypeResult(zv::Val type) -{ - if (UNEXPECTED(type.isUndef())) return zv::Val(); - zval reasons; - ZVAL_EMPTY_ARRAY(&reasons); - return pt_type_result_new(type.raw(), &reasons); -} - -/* TypeCombinator::union(...$types) of a list the resolver built */ -zv::Val unionOf(zv::Arr &types) -{ - HashTable *ht = types.table(); - uint32_t count = zend_hash_num_elements(ht); - if (count == 0) return pt_type_combinator_union(0, NULL); - if (EXPECTED(HT_IS_PACKED(ht) && HT_IS_WITHOUT_HOLES(ht))) return pt_type_combinator_union(count, ht->arPacked); - return pt_type_combinator_call_spread(PT_LC("union"), ht); -} - -/* TypeCombinator::union($a, $b) */ -zv::Val union2(zval *a, zval *b) -{ - zv::Args argv{a, b}; - return pt_type_combinator_union(2, argv); -} - -/* ConstantTypeHelper::getTypeFromValue($value) */ -zv::Val typeFromValue(zval *value) -{ - return pt_constant_type_helper_get_type_from_value(value); -} - -/* IntegerRangeType::fromInterval($min, $max) of int|null zvals; a float (the - * twin's arithmetic overflowed) raises the TypeError of the twin's - * strict-types call */ -zv::Val fromIntervalZv(zval *min, zval *max) -{ - bool minOk = Z_TYPE_P(min) == IS_LONG || Z_TYPE_P(min) == IS_NULL; - bool maxOk = Z_TYPE_P(max) == IS_LONG || Z_TYPE_P(max) == IS_NULL; - if (EXPECTED(minOk && maxOk)) return pt_integer_range_from_interval(NullableLong::from(min), NullableLong::from(max), 0); - zend_type_error("PHPStan\\Type\\IntegerRangeType::fromInterval(): Argument #%d ($%s) must be of type ?int, %s given", minOk ? 2 : 1, minOk ? "max" : "min", zend_zval_value_name(minOk ? max : min)); - return zv::Val(); -} - -zv::Val fromInterval(NullableLong min, NullableLong max) -{ - return pt_integer_range_from_interval(min, max, 0); -} - -/* TypeUtils::toBenevolentUnion($type) */ -zv::Val toBenevolentUnion(zval *type) -{ - return pt_type_call_static_ce(pt_ce_type_utils, PT_LC("tobenevolentunion"), 1, type); -} - -/* }}} */ - -/* {{{ PHP value arithmetic (the twin's operators, on the engine's functions) */ - -typedef zend_result (ZEND_FASTCALL *pt_ietr_binary_op)(zval *result, zval *op1, zval *op2); - -/* $a $b; UNDEF = pending exception */ -zv::Val phpOp(pt_ietr_binary_op fn, zval *a, zval *b) -{ - zval result; - ZVAL_UNDEF(&result); - if (UNEXPECTED(fn(&result, a, b) != SUCCESS || EG(exception) != NULL)) { - zval_ptr_dtor(&result); - return zv::Val(); - } - return zv::Val::adopt(result); -} - -/* an internal function of the engine's function table called directly - * (the function the twin calls: min(), max(), dirname()), its lookup cached - * in `cache`; UNDEF = pending exception */ -zv::Val callInternalFunction(zend_function *&cache, const char *name, size_t len, uint32_t argc, zval *argv) -{ - if (UNEXPECTED(cache == NULL)) { - cache = (zend_function *) zend_hash_str_find_ptr(CG(function_table), name, len); - if (UNEXPECTED(cache == NULL)) { - zend_throw_error(NULL, "Call to undefined function %s()", name); - return zv::Val(); - } - } - zval result; - ZVAL_UNDEF(&result); - zend_call_known_function(cache, NULL, NULL, &result, argc, argv, NULL); - if (UNEXPECTED(EG(exception) != NULL)) { - zval_ptr_dtor(&result); - return zv::Val(); - } - return zv::Val::adopt(result); -} - -/* min(...$argv) / max(...$argv) */ -zv::Val phpMinMax(bool max, uint32_t argc, zval *argv) -{ - static zend_function *minFn = NULL; - static zend_function *maxFn = NULL; - return max ? callInternalFunction(maxFn, PT_LC("max"), argc, argv) : callInternalFunction(minFn, PT_LC("min"), argc, argv); -} - -/* dirname($path) */ -zv::Val phpDirname(zval *path) -{ - static zend_function *dirnameFn = NULL; - return callInternalFunction(dirnameFn, PT_LC("dirname"), 1, path); -} - -/* $value === 0 || $value === 0.0 — in_array($value, [0, 0.0], true) */ -inline bool isZeroNumber(zval *value) -{ - return (Z_TYPE_P(value) == IS_LONG && Z_LVAL_P(value) == 0) || (Z_TYPE_P(value) == IS_DOUBLE && Z_DVAL_P(value) == 0.0); -} - -/* $a === $b for scalars */ -inline bool identical(zval *a, zval *b) -{ - return zend_is_identical(a, b); -} - -/* $a > $b / $a < $b (zend_compare, as the VM's IS_SMALLER) */ -inline bool greaterThan(zval *a, zval *b) -{ - return zend_compare(b, a) < 0; -} - -inline bool lessThan(zval *a, zval *b) -{ - return zend_compare(a, b) < 0; -} - -/* }}} */ - /* {{{ php-parser nodes */ NodeProp pt_ietr_name_name = PT_NAME_PROP; @@ -846,23 +367,6 @@ inline zv::Val callbackCallable(const pt_ietr_get_type &callback) namespace phpstanturbo { -/* the binary operators whose type the resolver computes */ -enum IetrBinaryKind : uint8_t -{ - IETR_PLUS, - IETR_MINUS, - IETR_MUL, - IETR_DIV, - IETR_MOD, - IETR_POW, - IETR_SHIFT_LEFT, - IETR_SHIFT_RIGHT, - IETR_BITWISE_AND, - IETR_BITWISE_OR, - IETR_BITWISE_XOR, - IETR_OTHER, -}; - inline constexpr int ietrBinaryKindClasses[IETR_OTHER] = { PT_CLASS_BINARY_OP_PLUS, PT_CLASS_BINARY_OP_MINUS, @@ -877,6 +381,35 @@ inline constexpr int ietrBinaryKindClasses[IETR_OTHER] = { PT_CLASS_BITWISE_XOR_EXPR, }; +/* the Type method each operator's getter calls on the left type */ +inline constexpr Method mArithmeticAdd{"arithmeticAdd"}; +inline constexpr Method mArithmeticSubtract{"arithmeticSubtract"}; +inline constexpr Method mArithmeticMultiply{"arithmeticMultiply"}; +inline constexpr Method mArithmeticDivide{"arithmeticDivide"}; +inline constexpr Method mArithmeticModulo{"arithmeticModulo"}; +inline constexpr Method mArithmeticPower{"arithmeticPower"}; +inline constexpr Method mArithmeticNegate{"arithmeticNegate"}; +inline constexpr Method mBitwiseShiftLeft{"bitwiseShiftLeft"}; +inline constexpr Method mBitwiseShiftRight{"bitwiseShiftRight"}; +inline constexpr Method mBitwiseAnd{"bitwiseAnd"}; +inline constexpr Method mBitwiseOr{"bitwiseOr"}; +inline constexpr Method mBitwiseXor{"bitwiseXor"}; +inline constexpr Method mStringConcatenate{"stringConcatenate"}; + +inline constexpr const Method *ietrBinaryKindMethods[IETR_OTHER] = { + &mArithmeticAdd, + &mArithmeticSubtract, + &mArithmeticMultiply, + &mArithmeticDivide, + &mArithmeticModulo, + &mArithmeticPower, + &mBitwiseShiftLeft, + &mBitwiseShiftRight, + &mBitwiseAnd, + &mBitwiseOr, + &mBitwiseXor, +}; + /* {{{ getType()'s instanceof chain: the arms in the twin's order */ enum IetrGetTypeArm : uint8_t @@ -1043,9 +576,9 @@ class InitializerExprTypeResolver public: explicit InitializerExprTypeResolver(zend_object *self) : self(self) {} - /* {{{ getConcatType() / resolveConcatType() */ + /* {{{ the operators */ - /* Mirrors getConcatType(). */ + /* getConcatType(): the operand types concatenated */ zv::Val getConcatType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const { IETR_VAL(leftType, getTypeOf(getTypeCallback, left)); @@ -1053,1384 +586,119 @@ class InitializerExprTypeResolver return resolveConcatType(leftType.raw(), rightType.raw()); } - /* Mirrors resolveConcatType(). */ + /* Mirrors resolveConcatType(): $left->stringConcatenate($right, $this->phpVersion) */ zv::Val resolveConcatType(zval *left, zval *right) const { - IETR_VAL(leftStringType, callOn(left, mToString)); - IETR_VAL(rightStringType, callOn(right, mToString)); - { - IETR_VAL(union_, union2(leftStringType.raw(), rightStringType.raw())); - if (isA(union_.raw(), pt_ce_error_type)) return newErrorType(); - } + zv::Args argv{right, prop(slots::phpVersion)}; + return callOn(left, mStringConcatenate, 2, argv); + } - int leftEmpty = isEmptyConstantString(leftStringType.raw()); - if (UNEXPECTED(leftEmpty < 0)) return zv::Val(); - if (leftEmpty) return rightStringType; + /* Mirrors getBitwiseAndType() / getBitwiseOrType() / getBitwiseXorType() / + * getModType() / getPlusType() / getMinusType() / getMulType() / + * getPowType() / getShiftLeftType() / getShiftRightType(): the + * operator extensions' type of `new BinaryOp\($left, $right)`, + * or the left type's operator method */ + zv::Val getOperatorType(IetrBinaryKind kind, zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const + { + IETR_VAL(leftType, getTypeOf(getTypeCallback, left)); + IETR_VAL(rightType, getTypeOf(getTypeCallback, right)); - int rightEmpty = isEmptyConstantString(rightStringType.raw()); - if (UNEXPECTED(rightEmpty < 0)) return zv::Val(); - if (rightEmpty) return leftStringType; + IETR_VAL(specifiedTypes, callOperatorExtensions(kind, left, right, leftType.raw(), rightType.raw())); + if (Z_TYPE_P(specifiedTypes.raw()) != IS_NULL) return specifiedTypes; - if (isA(leftStringType.raw(), pt_ce_constant_string_type) && isA(rightStringType.raw(), pt_ce_constant_string_type)) { - return callOn(leftStringType.raw(), mAppend, 1, rightStringType.raw()); - } + return callOperatorMethod(kind, leftType.raw(), rightType.raw()); + } - IETR_VAL(leftConstantStrings, callOn(leftStringType.raw(), mGetConstantStrings)); - IETR_VAL(rightConstantStrings, callOn(rightStringType.raw(), mGetConstantStrings)); - if (UNEXPECTED(!requireArray(leftConstantStrings.raw()) || !requireArray(rightConstantStrings.raw()))) return zv::Val(); - zend_long combinedConstantStringsCount = (zend_long) zend_hash_num_elements(Z_ARRVAL_P(leftConstantStrings.raw())) * (zend_long) zend_hash_num_elements(Z_ARRVAL_P(rightConstantStrings.raw())); - - // we limit the number of union-types for performance reasons - if (combinedConstantStringsCount > 0 && combinedConstantStringsCount <= PT_IETR_CALCULATE_SCALARS_LIMIT) { - zv::Arr strings = zv::Arr::create((uint32_t) combinedConstantStringsCount); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantStrings.raw())) { - zval *leftConstantString = leftEntry.value().raw(); - int leftStringEmpty = constantStringValueIsEmpty(leftConstantString); - if (UNEXPECTED(leftStringEmpty < 0)) return zv::Val(); - if (leftStringEmpty) { - // $strings = array_merge($strings, $rightConstantStrings) - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantStrings.raw())) { - if (rightEntry.hasStringKey()) { - strings.set(rightEntry.stringKey(), zv::Val::copyOf(rightEntry.value())); - } else { - strings.push(rightEntry.value()); - } - } - continue; - } + /* Mirrors getDivType(). */ + zv::Val getDivType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const + { + IETR_VAL(leftType, getTypeOf(getTypeCallback, left)); + IETR_VAL(rightType, getTypeOf(getTypeCallback, right)); - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantStrings.raw())) { - zval *rightConstantString = rightEntry.value().raw(); - int rightStringEmpty = constantStringValueIsEmpty(rightConstantString); - if (UNEXPECTED(rightStringEmpty < 0)) return zv::Val(); - if (rightStringEmpty) { - strings.push(zv::Ref(leftConstantString)); - continue; - } + IETR_VAL(specifiedTypes, callOperatorExtensions(IETR_DIV, left, right, leftType.raw(), rightType.raw())); + if (Z_TYPE_P(specifiedTypes.raw()) != IS_NULL) return specifiedTypes; - IETR_VAL(appended, callOn(leftConstantString, mAppend, 1, rightConstantString)); - strings.push(std::move(appended)); - } - } + IETR_VAL(result, callOperatorMethod(IETR_DIV, leftType.raw(), rightType.raw())); - if (zend_hash_num_elements(strings.table()) > 0) { - return unionOf(strings); - } - } + // the remainder can be narrowed in the scope, e.g. by `$a % $b === 0` + IETR_TRI(leftIsInteger, triOn(leftType.raw(), mIsInteger)); + if (leftIsInteger == PT_TRI_YES) { + IETR_TRI(rightIsInteger, triOn(rightType.raw(), mIsInteger)); + if (rightIsInteger == PT_TRI_YES) { + IETR_VAL(modNode, newBinaryOpNode(PT_CLASS_MOD_EXPR, left, right)); + IETR_VAL(modType, getTypeOf(getTypeCallback, modNode.raw())); + IETR_TRI(modIsInteger, triOn(modType.raw(), mIsInteger)); + if (modIsInteger == PT_TRI_YES) { + IETR_VAL(zero, newConstantIntegerType(0)); + IETR_TRI(zeroIsSuperTypeOfMod, triOn(zero.raw(), mIsSuperTypeOf, 1, modType.raw())); + if (zeroIsSuperTypeOfMod == PT_TRI_YES) { + IETR_VAL(floatType, newFloatType()); + IETR_VAL(withoutFloat, pt_type_combinator_remove(result.raw(), floatType.raw())); - zv::Arr accessoryTypes = zv::Arr::create(6); - { - IETR_TRI(leftNonEmpty, triOn(leftStringType.raw(), mIsNonEmptyString)); - IETR_TRI(rightNonEmpty, triOn(rightStringType.raw(), mIsNonEmptyString)); - if (pt_trinary_and(leftNonEmpty, rightNonEmpty) == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_non_falsy_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); - } else { - IETR_TRI(leftNonFalsy, triOn(leftStringType.raw(), mIsNonFalsyString)); - IETR_TRI(rightNonFalsy, triOn(rightStringType.raw(), mIsNonFalsyString)); - if (pt_trinary_or(leftNonFalsy, rightNonFalsy) == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_non_falsy_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); - } else { - IETR_TRI(leftNonEmpty2, triOn(leftStringType.raw(), mIsNonEmptyString)); - IETR_TRI(rightNonEmpty2, triOn(rightStringType.raw(), mIsNonEmptyString)); - if (pt_trinary_or(leftNonEmpty2, rightNonEmpty2) == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_non_empty_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); + // PHP_INT_MIN / -1 divides without a remainder but still overflows to a float + if (!isA(withoutFloat.raw(), pt_ce_never_type)) { + return withoutFloat; + } } } } } - { - IETR_TRI(leftLiteral, triOn(leftStringType.raw(), mIsLiteralString)); - IETR_TRI(rightLiteral, triOn(rightStringType.raw(), mIsLiteralString)); - if (pt_trinary_and(leftLiteral, rightLiteral) == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_literal_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); - } - } - - { - IETR_TRI(leftLowercase, triOn(leftStringType.raw(), mIsLowercaseString)); - IETR_TRI(rightLowercase, triOn(rightStringType.raw(), mIsLowercaseString)); - if (pt_trinary_and(leftLowercase, rightLowercase) == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_lowercase_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); - } - } - - { - IETR_TRI(leftUppercase, triOn(leftStringType.raw(), mIsUppercaseString)); - IETR_TRI(rightUppercase, triOn(rightStringType.raw(), mIsUppercaseString)); - if (pt_trinary_and(leftUppercase, rightUppercase) == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_uppercase_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); - } - } - - { - zval emptyString; - ZVAL_EMPTY_STRING(&emptyString); - IETR_VAL(emptyStringType, newConstantStringType(Z_STR(emptyString))); - IETR_VAL(leftNumericStringNonEmpty, pt_type_combinator_remove(leftStringType.raw(), emptyStringType.raw())); - IETR_TRI(leftNumeric, triOn(leftNumericStringNonEmpty.raw(), mIsNumericString)); - if (leftNumeric == PT_TRI_YES) { - IETR_TRI(leftIsInteger, triOn(left, mIsInteger)); - bool integerValidation = leftIsInteger == PT_TRI_YES; - - bool allRightConstantsZeroOrMore = false; - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantStrings.raw())) { - zval *rightConstantString = rightEntry.value().raw(); - IETR_VAL(value, constantStringValue(rightConstantString)); - if (UNEXPECTED(Z_TYPE_P(value.raw()) != IS_STRING)) { - // ConstantStringType::getValue(): string - pt_throw_should_not_happen(); - return zv::Val(); - } - zend_string *string = Z_STR_P(value.raw()); - if (ZSTR_LEN(string) == 0) { - continue; - } - - if ( - is_numeric_string(ZSTR_VAL(string), ZSTR_LEN(string), NULL, NULL, false) == 0 - || !(integerValidation ? ZSTR_VAL(string)[0] != '-' : isDecimalDigits(string)) - ) { - allRightConstantsZeroOrMore = false; - break; - } - - allRightConstantsZeroOrMore = true; - } - - bool nonNegativeRight = allRightConstantsZeroOrMore; - if (!nonNegativeRight) { - IETR_VAL(zeroOrMoreInteger, fromInterval(NullableLong::of(0), NullableLong::null())); - IETR_TRI(zeroOrMoreRight, triOn(zeroOrMoreInteger.raw(), mIsSuperTypeOf, 1, right)); - nonNegativeRight = zeroOrMoreRight == PT_TRI_YES; - } - if (nonNegativeRight) { - zval accessory; - if (UNEXPECTED(!pt_accessory_numeric_string_type_new(&accessory))) return zv::Val(); - accessoryTypes.push(zv::Val::adopt(accessory)); - } - } - } + return result; + } - if (zend_hash_num_elements(accessoryTypes.table()) > 0) { - IETR_VAL(stringType, newStringType()); - accessoryTypes.push(std::move(stringType)); - return newIntersectionType(accessoryTypes); - } + /* $this->operatorTypeSpecifyingExtensionRegistry->callOperatorTypeSpecifyingExtensions(new BinaryOp\($left, $right), $leftType, $rightType) */ + zv::Val callOperatorExtensions(IetrBinaryKind kind, zval *left, zval *right, zval *leftType, zval *rightType) const + { + IETR_VAL(node, newBinaryOpNode(ietrBinaryKindClasses[kind], left, right)); + return callOperatorTypeSpecifyingExtensions(prop(slots::operatorTypeSpecifyingExtensionRegistry), node.raw(), leftType, rightType); + } - return newStringType(); + /* $leftType->($rightType, $this->phpVersion) */ + zv::Val callOperatorMethod(IetrBinaryKind kind, zval *leftType, zval *rightType) const + { + zv::Args argv{rightType, prop(slots::phpVersion)}; + return callOn(leftType, *ietrBinaryKindMethods[kind], 2, argv); } /* }}} */ - /* {{{ the bitwise operators */ + /* {{{ getSpaceshipType() */ - /* Mirrors getBitwiseAndType() / getBitwiseOrType() / getBitwiseXorType() - * (kind: IETR_BITWISE_AND / _OR / _XOR — the three bodies differ only in - * the node class, the operation and the range helper) */ - zv::Val getBitwiseType(IetrBinaryKind kind, zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const + /* Mirrors getSpaceshipType(). */ + zv::Val getSpaceshipType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const { - IETR_VAL(leftTypeHold, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightTypeHold, getTypeOf(getTypeCallback, right)); - zval *leftType = leftTypeHold.raw(); - zval *rightType = rightTypeHold.raw(); - - { - IETR_VAL(node, newBinaryOpNode(ietrBinaryKindClasses[kind], left, right)); - IETR_VAL(specifiedTypes, callOperatorTypeSpecifyingExtensions(prop(slots::operatorTypeSpecifyingExtensionRegistry), node.raw(), leftType, rightType)); - if (Z_TYPE_P(specifiedTypes.raw()) != IS_NULL) return specifiedTypes; - } - - if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { - return getNeverType(leftType, rightType); - } - - zv::Val result; - zend_long code = getFiniteOrConstantScalarTypes(leftType, rightType, kind, result); - if (UNEXPECTED(code < 0)) return zv::Val(); - if (code == 0) return result; - - // computed before optimizeScalarType() below widens integer ranges to int - IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - - zv::Val optimizedLeft; - zv::Val optimizedRight; - if (code == PT_IETR_IS_SCALAR_TYPE) { - optimizedLeft = optimizeScalarType(leftType); - if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); - leftType = optimizedLeft.raw(); - optimizedRight = optimizeScalarType(rightType); - if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); - rightType = optimizedRight.raw(); - } - - bool leftMixed = isA(leftType, pt_ce_mixed_type); - bool rightMixed = isA(rightType, pt_ce_mixed_type); - if (leftMixed && rightMixed) { - IETR_VAL(integerType, newIntegerType()); - IETR_VAL(stringType, newStringType()); - zv::Arr types = zv::Arr::create(2); - types.push(std::move(integerType)); - types.push(std::move(stringType)); - return newBenevolentUnionType(types); - } - - IETR_TRI(leftIsString, triOn(leftType, mIsString)); - IETR_TRI(rightIsString, triOn(rightType, mIsString)); - if ( - (leftIsString == PT_TRI_YES || leftMixed) - && (rightIsString == PT_TRI_YES || rightMixed) - ) { - return newStringType(); - } - if (leftIsString == PT_TRI_MAYBE && rightIsString == PT_TRI_MAYBE) { - return newErrorType(); - } - - { - IETR_VAL(leftToNumber, callOn(leftType, mToNumber)); - if (isA(leftToNumber.raw(), pt_ce_error_type)) return newErrorType(); - IETR_VAL(rightToNumber, callOn(rightType, mToNumber)); - if (isA(rightToNumber.raw(), pt_ce_error_type)) return newErrorType(); - } - - zv::Val range = kind == IETR_BITWISE_AND - ? computeBitwiseAndRange(leftNumberType.raw(), rightNumberType.raw()) - : computeBitwiseOrXorRange(leftNumberType.raw(), rightNumberType.raw()); - if (UNEXPECTED(range.isUndef())) return zv::Val(); - if (Z_TYPE_P(range.raw()) != IS_NULL) return range; - - return newIntegerType(); - } - - /* Mirrors getFiniteOrConstantScalarTypes() with the operation of `kind`: - * 0 = the Type in `result`, PT_IETR_IS_SCALAR_TYPE / PT_IETR_IS_UNKNOWN, - * -1 = pending exception */ - zend_long getFiniteOrConstantScalarTypes(zval *leftType, zval *rightType, IetrBinaryKind kind, zv::Val &result) const - { - pt_ietr_binary_op operation = kind == IETR_BITWISE_AND ? bitwise_and_function : (kind == IETR_BITWISE_OR ? bitwise_or_function : bitwise_xor_function); - return getFiniteOrConstantScalarTypesWith(leftType, rightType, operation, NULL, result); - } - - /* the same with the operation as the engine's operator function or, for - * a PHP caller of the private method, its $operationCallable */ - static zend_long getFiniteOrConstantScalarTypesWith(zval *leftType, zval *rightType, pt_ietr_binary_op operation, zval *operationCallable, zv::Val &result) - { - zv::Val leftTypes = isA(leftType, pt_ce_integer_range_type) ? callOn(leftType, mGetFiniteTypes) : callOn(leftType, mGetConstantScalarTypes); - if (UNEXPECTED(leftTypes.isUndef())) return -1; - zv::Val rightTypes = isA(rightType, pt_ce_integer_range_type) ? callOn(rightType, mGetFiniteTypes) : callOn(rightType, mGetConstantScalarTypes); - if (UNEXPECTED(rightTypes.isUndef())) return -1; - if (UNEXPECTED(!requireArray(leftTypes.raw()) || !requireArray(rightTypes.raw()))) return -1; - - zend_long leftTypesCount = zend_hash_num_elements(Z_ARRVAL_P(leftTypes.raw())); - zend_long rightTypesCount = zend_hash_num_elements(Z_ARRVAL_P(rightTypes.raw())); - - if (leftTypesCount == 0 || rightTypesCount == 0) { - return PT_IETR_IS_UNKNOWN; - } - - bool generalize = leftTypesCount * rightTypesCount > PT_IETR_CALCULATE_SCALARS_LIMIT; - if (generalize) { - return PT_IETR_IS_SCALAR_TYPE; - } - - zend_class_entry *constantScalarCe = pt_class(PT_CLASS_CONSTANT_SCALAR_TYPE); - if (UNEXPECTED(constantScalarCe == NULL)) return -1; - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftTypesCount * rightTypesCount)); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftTypes.raw())) { - zval *leftTypeInner = leftEntry.value().raw(); - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightTypes.raw())) { - zval *rightTypeInner = rightEntry.value().raw(); - zv::Val resultType; - if (isA(leftTypeInner, pt_ce_constant_string_type) && isA(rightTypeInner, pt_ce_constant_string_type)) { - zv::Val leftValue = callOn(leftTypeInner, mGetValue); - if (UNEXPECTED(leftValue.isUndef())) return -1; - zv::Val rightValue = callOn(rightTypeInner, mGetValue); - if (UNEXPECTED(rightValue.isUndef())) return -1; - zv::Val resultValue = applyOperation(operation, operationCallable, leftValue.raw(), rightValue.raw()); - if (UNEXPECTED(resultValue.isUndef())) return -1; - resultType = typeFromValue(resultValue.raw()); - } else { - zv::Val leftNumberType = callOn(leftTypeInner, mToNumber); - if (UNEXPECTED(leftNumberType.isUndef())) return -1; - zv::Val rightNumberType = callOn(rightTypeInner, mToNumber); - if (UNEXPECTED(rightNumberType.isUndef())) return -1; - - if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - - if (!isA(leftNumberType.raw(), constantScalarCe) || !isA(rightNumberType.raw(), constantScalarCe)) { - pt_throw_should_not_happen(); - return -1; - } - - zv::Val leftValue = callOn(leftNumberType.raw(), mGetValue); - if (UNEXPECTED(leftValue.isUndef())) return -1; - zv::Val rightValue = callOn(rightNumberType.raw(), mGetValue); - if (UNEXPECTED(rightValue.isUndef())) return -1; - zv::Val resultValue = applyOperation(operation, operationCallable, leftValue.raw(), rightValue.raw()); - if (UNEXPECTED(resultValue.isUndef())) return -1; - resultType = typeFromValue(resultValue.raw()); - } - if (UNEXPECTED(resultType.isUndef())) return -1; - resultTypes.push(std::move(resultType)); - } - } - result = unionOf(resultTypes); - return result.isUndef() ? -1 : 0; - } - - /* $operationCallable($a, $b) or the operator */ - static zv::Val applyOperation(pt_ietr_binary_op operation, zval *operationCallable, zval *a, zval *b) - { - if (operationCallable != NULL) { - zv::Args argv{a, b}; - return pt_type_call_callable(operationCallable, 2, argv); - } - return phpOp(operation, a, b); - } - - /* }}} */ - - /* {{{ getSpaceshipType() */ - - /* Mirrors getSpaceshipType(). */ - zv::Val getSpaceshipType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftTypes, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightTypes, getTypeOf(getTypeCallback, right)); + IETR_VAL(leftTypes, getTypeOf(getTypeCallback, left)); + IETR_VAL(rightTypes, getTypeOf(getTypeCallback, right)); if (isA(leftTypes.raw(), pt_ce_never_type) || isA(rightTypes.raw(), pt_ce_never_type)) { - return getNeverType(leftTypes.raw(), rightTypes.raw()); - } - - IETR_VAL(leftValues, callOn(leftTypes.raw(), mGetConstantScalarValues)); - IETR_VAL(rightValues, callOn(rightTypes.raw(), mGetConstantScalarValues)); - if (UNEXPECTED(!requireArray(leftValues.raw()) || !requireArray(rightValues.raw()))) return zv::Val(); - - zend_long leftValuesCount = zend_hash_num_elements(Z_ARRVAL_P(leftValues.raw())); - zend_long rightValuesCount = zend_hash_num_elements(Z_ARRVAL_P(rightValues.raw())); - if (leftValuesCount > 0 && rightValuesCount > 0 && leftValuesCount * rightValuesCount <= PT_IETR_CALCULATE_SCALARS_LIMIT) { - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftValuesCount * rightValuesCount)); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftValues.raw())) { - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightValues.raw())) { - /* LooseComparisonHelper::compareValues($leftValue, $rightValue, $this->phpVersion) ?? 1 */ - zv::Args args{leftEntry.value().raw(), rightEntry.value().raw(), prop(slots::phpVersion)}; - zv::Val compared = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("comparevalues"), 3, args); - if (UNEXPECTED(compared.isUndef())) return zv::Val(); - zval comparison; - ZVAL_LONG(&comparison, Z_TYPE_P(compared.raw()) == IS_LONG ? Z_LVAL_P(compared.raw()) : 1); - IETR_VAL(resultType, typeFromValue(&comparison)); - resultTypes.push(std::move(resultType)); - } - } - return unionOf(resultTypes); - } - - return fromInterval(NullableLong::of(-1), NullableLong::of(1)); - } - - /* }}} */ - - /* {{{ the arithmetic operators */ - - /* the constant-scalar product of getPlusType() / getMinusType() / - * getMulType() / getDivTypeFromTypes() / getModType() / - * getShiftLeftType() / getShiftRightType(): 0 = the Type in `result` - * (the union, or an ErrorType the twin returns from inside the loop), - * 1 = generalize (the twin's optimizeScalarType() of both sides follows), - * 2 = not both sides constant, -1 = pending exception */ - int constantScalarArithmetic(zval *leftType, zval *rightType, IetrBinaryKind kind, zv::Val &result) const - { - zv::Val leftTypes = callOn(leftType, mGetConstantScalarTypes); - if (UNEXPECTED(leftTypes.isUndef())) return -1; - zv::Val rightTypes = callOn(rightType, mGetConstantScalarTypes); - if (UNEXPECTED(rightTypes.isUndef())) return -1; - if (UNEXPECTED(!requireArray(leftTypes.raw()) || !requireArray(rightTypes.raw()))) return -1; - zend_long leftTypesCount = zend_hash_num_elements(Z_ARRVAL_P(leftTypes.raw())); - zend_long rightTypesCount = zend_hash_num_elements(Z_ARRVAL_P(rightTypes.raw())); - if (leftTypesCount <= 0 || rightTypesCount <= 0) return 2; - - bool generalize = leftTypesCount * rightTypesCount > PT_IETR_CALCULATE_SCALARS_LIMIT; - if (generalize) return 1; - - zend_class_entry *constantScalarCe = pt_class(PT_CLASS_CONSTANT_SCALAR_TYPE); - if (UNEXPECTED(constantScalarCe == NULL)) return -1; - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftTypesCount * rightTypesCount)); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftTypes.raw())) { - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightTypes.raw())) { - zv::Val leftNumberType = callOn(leftEntry.value().raw(), mToNumber); - if (UNEXPECTED(leftNumberType.isUndef())) return -1; - zv::Val rightNumberType = callOn(rightEntry.value().raw(), mToNumber); - if (UNEXPECTED(rightNumberType.isUndef())) return -1; - - if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - - if (!isA(leftNumberType.raw(), constantScalarCe) || !isA(rightNumberType.raw(), constantScalarCe)) { - pt_throw_should_not_happen(); - return -1; - } - - zv::Val leftValue = callOn(leftNumberType.raw(), mGetValue); - if (UNEXPECTED(leftValue.isUndef())) return -1; - zv::Val rightValue = callOn(rightNumberType.raw(), mGetValue); - if (UNEXPECTED(rightValue.isUndef())) return -1; - - zv::Val resultValue; - switch (kind) { - case IETR_PLUS: - resultValue = phpOp(add_function, leftValue.raw(), rightValue.raw()); - break; - case IETR_MINUS: - resultValue = phpOp(sub_function, leftValue.raw(), rightValue.raw()); - break; - case IETR_MUL: - resultValue = phpOp(mul_function, leftValue.raw(), rightValue.raw()); - break; - case IETR_DIV: - if (isZeroNumber(rightValue.raw())) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - resultValue = phpOp(div_function, leftValue.raw(), rightValue.raw()); - break; - case IETR_MOD: { - zval rightInteger; - ZVAL_LONG(&rightInteger, zval_get_long(rightValue.raw())); - if (Z_LVAL(rightInteger) == 0) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - zval leftInteger; - ZVAL_LONG(&leftInteger, zval_get_long(leftValue.raw())); - resultValue = phpOp(mod_function, &leftInteger, &rightInteger); - break; - } - case IETR_SHIFT_LEFT: - case IETR_SHIFT_RIGHT: { - zval zero; - ZVAL_LONG(&zero, 0); - if (lessThan(rightValue.raw(), &zero)) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - zval leftInteger, rightInteger; - ZVAL_LONG(&leftInteger, zval_get_long(leftValue.raw())); - ZVAL_LONG(&rightInteger, zval_get_long(rightValue.raw())); - resultValue = phpOp(kind == IETR_SHIFT_LEFT ? shift_left_function : shift_right_function, &leftInteger, &rightInteger); - break; - } - default: - pt_throw_should_not_happen(); - return -1; - } - if (UNEXPECTED(resultValue.isUndef())) return -1; - zv::Val resultType = typeFromValue(resultValue.raw()); - if (UNEXPECTED(resultType.isUndef())) return -1; - resultTypes.push(std::move(resultType)); - } - } - - result = unionOf(resultTypes); - return result.isUndef() ? -1 : 0; - } - - /* Mirrors getDivType(). */ - zv::Val getDivType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftType, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightType, getTypeOf(getTypeCallback, right)); - - IETR_VAL(result, getDivTypeFromTypes(left, right, leftType.raw(), rightType.raw())); - - IETR_TRI(leftIsInteger, triOn(leftType.raw(), mIsInteger)); - if (leftIsInteger == PT_TRI_YES) { - IETR_TRI(rightIsInteger, triOn(rightType.raw(), mIsInteger)); - if (rightIsInteger == PT_TRI_YES) { - IETR_VAL(modNode, newBinaryOpNode(PT_CLASS_MOD_EXPR, left, right)); - IETR_VAL(modType, getTypeOf(getTypeCallback, modNode.raw())); - IETR_TRI(modIsInteger, triOn(modType.raw(), mIsInteger)); - if (modIsInteger == PT_TRI_YES) { - IETR_VAL(zero, newConstantIntegerType(0)); - IETR_TRI(zeroIsSuperTypeOfMod, triOn(zero.raw(), mIsSuperTypeOf, 1, modType.raw())); - if (zeroIsSuperTypeOfMod == PT_TRI_YES) { - IETR_VAL(floatType, newFloatType()); - IETR_VAL(withoutFloat, pt_type_combinator_remove(result.raw(), floatType.raw())); - - // PHP_INT_MIN / -1 divides without a remainder but still overflows to a float - if (!isA(withoutFloat.raw(), pt_ce_never_type)) { - return withoutFloat; - } - } - } - } - } - - return result; - } - - /* Mirrors getDivTypeFromTypes(). */ - zv::Val getDivTypeFromTypes(zval *left, zval *right, zval *leftType, zval *rightType) const - { - zv::Val result; - int constant = constantScalarArithmetic(leftType, rightType, IETR_DIV, result); - if (UNEXPECTED(constant < 0)) return zv::Val(); - if (constant == 0) return result; - - zv::Val optimizedLeft; - zv::Val optimizedRight; - if (constant == 1) { - optimizedLeft = optimizeScalarType(leftType); - if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); - leftType = optimizedLeft.raw(); - optimizedRight = optimizeScalarType(rightType); - if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); - rightType = optimizedRight.raw(); - } - - { - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - IETR_VAL(rightScalarValues, callOn(rightNumberType.raw(), mGetConstantScalarValues)); - if (UNEXPECTED(!requireArray(rightScalarValues.raw()))) return zv::Val(); - for (zv::ArrayEntry entry : zv::ArrRef(rightScalarValues.raw())) { - if (isZeroNumber(entry.value().raw())) { - return newErrorType(); - } - } - } - - return resolveCommonMath(IETR_DIV, left, right, NULL, leftType, rightType); - } - - /* Mirrors getModType(). */ - zv::Val getModType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftTypeHold, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightTypeHold, getTypeOf(getTypeCallback, right)); - zval *leftType = leftTypeHold.raw(); - zval *rightType = rightTypeHold.raw(); - - if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { - return getNeverType(leftType, rightType); - } - - { - IETR_VAL(node, newBinaryOpNode(PT_CLASS_MOD_EXPR, left, right)); - IETR_VAL(extensionSpecified, callOperatorTypeSpecifyingExtensions(prop(slots::operatorTypeSpecifyingExtensionRegistry), node.raw(), leftType, rightType)); - if (Z_TYPE_P(extensionSpecified.raw()) != IS_NULL) return extensionSpecified; - } - - { - IETR_VAL(leftToNumber, callOn(leftType, mToNumber)); - if (isA(leftToNumber.raw(), pt_ce_error_type)) return newErrorType(); - IETR_VAL(rightToNumber, callOn(rightType, mToNumber)); - if (isA(rightToNumber.raw(), pt_ce_error_type)) return newErrorType(); - } - - zv::Val result; - int constant = constantScalarArithmetic(leftType, rightType, IETR_MOD, result); - if (UNEXPECTED(constant < 0)) return zv::Val(); - if (constant == 0) return result; - - zv::Val optimizedLeft; - zv::Val optimizedRight; - if (constant == 1) { - optimizedLeft = optimizeScalarType(leftType); - if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); - leftType = optimizedLeft.raw(); - optimizedRight = optimizeScalarType(rightType); - if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); - rightType = optimizedRight.raw(); - } - - { - IETR_VAL(integerType, callOn(rightType, mToInteger)); - if (isA(integerType.raw(), pt_ce_constant_integer_type)) { - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(integerType.raw()), value))) return zv::Val(); - if (value == 1) return newConstantIntegerType(0); - } - } - - { - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - IETR_VAL(rightScalarValues, callOn(rightNumberType.raw(), mGetConstantScalarValues)); - if (UNEXPECTED(!requireArray(rightScalarValues.raw()))) return zv::Val(); - for (zv::ArrayEntry entry : zv::ArrRef(rightScalarValues.raw())) { - if (isZeroNumber(entry.value().raw())) { - return newErrorType(); - } - } - } - - NullableLong maxMagnitude = NullableLong::null(); - { - IETR_VAL(rightInteger, callOn(rightType, mToInteger)); - bool hasDivisorBounds; - NullableLong divisorMin, divisorMax; - if (UNEXPECTED(!getIntegerBounds(rightInteger.raw(), hasDivisorBounds, divisorMin, divisorMax))) return zv::Val(); - if (hasDivisorBounds) { - maxMagnitude = getMaxModuloMagnitude(divisorMin, divisorMax); - } - } - - bool hasLeftBounds; - NullableLong leftMin, leftMax; - { - IETR_VAL(leftInteger, callOn(leftType, mToInteger)); - if (UNEXPECTED(!getIntegerBounds(leftInteger.raw(), hasLeftBounds, leftMin, leftMax))) return zv::Val(); - } - if (!hasLeftBounds) { - leftMin = NullableLong::null(); - leftMax = NullableLong::null(); - } - if (leftMin.isNull) { - IETR_VAL(nonNegative, fromInterval(NullableLong::of(0), NullableLong::null())); - IETR_TRI(leftIsNonNegative, triOn(nonNegative.raw(), mIsSuperTypeOf, 1, leftType)); - if (leftIsNonNegative == PT_TRI_YES) leftMin = NullableLong::of(0); - } - if (leftMax.isNull) { - IETR_VAL(nonPositive, fromInterval(NullableLong::null(), NullableLong::of(0))); - IETR_TRI(leftIsNonPositive, triOn(nonPositive.raw(), mIsSuperTypeOf, 1, leftType)); - if (leftIsNonPositive == PT_TRI_YES) leftMax = NullableLong::of(0); - } - - // The result has the sign of the dividend and is never bigger in magnitude than either - // the dividend or the largest magnitude the divisor can have. - NullableLong rangeMax; - if (!leftMax.isNull && leftMax.value <= 0) { - rangeMax = NullableLong::of(0); - } else if (maxMagnitude.isNull) { - rangeMax = leftMax; - } else if (leftMax.isNull) { - rangeMax = maxMagnitude; - } else { - rangeMax = NullableLong::of(std::min(leftMax.value, maxMagnitude.value)); - } - - NullableLong rangeMin; - if (!leftMin.isNull && leftMin.value >= 0) { - rangeMin = NullableLong::of(0); - } else if (maxMagnitude.isNull) { - rangeMin = leftMin; - } else if (leftMin.isNull) { - rangeMin = NullableLong::of(-maxMagnitude.value); - } else { - rangeMin = NullableLong::of(std::max(leftMin.value, -maxMagnitude.value)); - } - - 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 - * as unbounded too: abs(PHP_INT_MIN) does not fit into an integer, and the - * bound it stands for is PHP_INT_MAX, which is all an unbounded result can - * hold anyway */ - static NullableLong getMaxModuloMagnitude(NullableLong divisorMin, NullableLong divisorMax) - { - if (divisorMin.isNull || divisorMax.isNull || divisorMin.value == ZEND_LONG_MIN) return NullableLong::null(); - zend_long absMin = divisorMin.value < 0 ? -divisorMin.value : divisorMin.value; - zend_long absMax = divisorMax.value < 0 ? -divisorMax.value : divisorMax.value; - zend_long magnitude = std::max(absMin, absMax); - if (magnitude == 0) return NullableLong::null(); - return NullableLong::of(magnitude - 1); - } - - /* 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 */ - [[nodiscard]] bool getIntegerBounds(zval *type, bool &has, NullableLong &outMin, NullableLong &outMax) const - { - 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; - } - - 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) { - unboundedMin = true; - } else if (min.isNull || innerMin.value < min.value) { - min = innerMin; - } - - if (innerMax.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; - } - } - - has = true; - outMin = unboundedMin ? NullableLong::null() : min; - outMax = unboundedMax ? NullableLong::null() : max; - return true; - } - - /* getIntegerBounds() as the private method returns it: the [min, max] - * pair or null; UNDEF = pending exception */ - zv::Val getIntegerBoundsValue(zval *type) const - { - bool has = false; - NullableLong min = NullableLong::null(); - NullableLong max = NullableLong::null(); - if (UNEXPECTED(!getIntegerBounds(type, has, min, max))) return zv::Val(); - if (!has) return zv::Val::null(); - zv::Arr pair = zv::Arr::create(2); - pair.push(min.toVal()); - pair.push(max.toVal()); - 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 getPlusType(). */ - zv::Val getPlusType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftTypeHold, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightTypeHold, getTypeOf(getTypeCallback, right)); - zval *leftType = leftTypeHold.raw(); - zval *rightType = rightTypeHold.raw(); - - if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { - return getNeverType(leftType, rightType); - } - - zv::Val result; - int constant = constantScalarArithmetic(leftType, rightType, IETR_PLUS, result); - if (UNEXPECTED(constant < 0)) return zv::Val(); - if (constant == 0) return result; - - zv::Val optimizedLeft; - zv::Val optimizedRight; - if (constant == 1) { - optimizedLeft = optimizeScalarType(leftType); - if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); - leftType = optimizedLeft.raw(); - optimizedRight = optimizeScalarType(rightType); - if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); - rightType = optimizedRight.raw(); - } - - IETR_VAL(leftConstantArrays, callOn(leftType, mGetConstantArrays)); - IETR_VAL(rightConstantArrays, callOn(rightType, mGetConstantArrays)); - if (UNEXPECTED(!requireArray(leftConstantArrays.raw()) || !requireArray(rightConstantArrays.raw()))) return zv::Val(); - - zend_long leftCount = zend_hash_num_elements(Z_ARRVAL_P(leftConstantArrays.raw())); - zend_long rightCount = zend_hash_num_elements(Z_ARRVAL_P(rightConstantArrays.raw())); - if (leftCount > 0 && rightCount > 0 - && (leftCount + rightCount < PT_CONSTANT_ARRAY_TYPE_BUILDER_ARRAY_COUNT_LIMIT)) { - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftCount * rightCount)); - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantArrays.raw())) { - zval *rightConstantArray = rightEntry.value().raw(); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { - zval *leftConstantArray = leftEntry.value().raw(); - IETR_VAL(newArrayBuilder, pt_constant_array_type_builder_create_from_constant_array(rightConstantArray)); - IETR_VAL(leftKeyTypes, callOn(leftConstantArray, mGetKeyTypes)); - if (UNEXPECTED(!requireArray(leftKeyTypes.raw()))) return zv::Val(); - for (zv::ArrayEntry keyEntry : zv::ArrRef(leftKeyTypes.raw())) { - zval *leftKeyType = keyEntry.value().raw(); - zval i; - entryKey(keyEntry, i); - IETR_VAL(optionalHold, callOn(leftConstantArray, mIsOptionalKey, 1, &i)); - bool optional = zend_is_true(optionalHold.raw()); - IETR_VAL(valueType, callOn(leftConstantArray, mGetOffsetValueType, 1, leftKeyType)); - zv::Val valueTypeHold = std::move(valueType); - if (!optional) { - IETR_TRI(rightHas, triOn(rightConstantArray, mHasOffsetValueType, 1, leftKeyType)); - if (rightHas == PT_TRI_MAYBE) { - IETR_VAL(rightValueType, callOn(rightConstantArray, mGetOffsetValueType, 1, leftKeyType)); - IETR_VAL(merged, union2(valueTypeHold.raw(), rightValueType.raw())); - valueTypeHold = std::move(merged); - } - } - if (UNEXPECTED(!pt_constant_array_type_builder_set_offset_value_type(newArrayBuilder.raw(), leftKeyType, valueTypeHold.raw(), optional))) return zv::Val(); - } - IETR_VAL(array, pt_constant_array_type_builder_get_array(newArrayBuilder.raw())); - resultTypes.push(std::move(array)); - } - } - return unionOf(resultTypes); - } - - // `array{...} + array` keeps the known keyed prefix and - // folds the right-hand side into an open `...` tail. This - // only applies when the left arrays' sealedness is known (bleeding edge); - // otherwise the legacy behavior below is kept for backward compatibility. - if (leftCount > 0 && rightCount == 0 - && leftCount < PT_CONSTANT_ARRAY_TYPE_BUILDER_ARRAY_COUNT_LIMIT) { - IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); - if (rightIsArray == PT_TRI_YES) { - bool allSealednessKnown = true; - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { - IETR_TRI(unsealed, triOn(leftEntry.value().raw(), mIsUnsealed)); - if (unsealed == PT_TRI_MAYBE) { - allSealednessKnown = false; - break; - } - } - - if (allSealednessKnown) { - IETR_VAL(rightKeyType, callOn(rightType, mGetIterableKeyType)); - IETR_VAL(rightValueType, callOn(rightType, mGetIterableValueType)); - zv::Arr resultTypes = zv::Arr::create((uint32_t) leftCount); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { - zval *leftConstantArray = leftEntry.value().raw(); - IETR_VAL(newArrayBuilder, pt_constant_array_type_builder_create_from_constant_array(leftConstantArray)); - IETR_VAL(existingUnsealed, callOn(leftConstantArray, mGetUnsealedTypes)); - IETR_TRI(unsealed, triOn(leftConstantArray, mIsUnsealed)); - if (unsealed == PT_TRI_YES && Z_TYPE_P(existingUnsealed.raw()) != IS_NULL) { - zval *existingKey = arrayIndex(existingUnsealed.raw(), 0); - if (UNEXPECTED(existingKey == NULL)) return zv::Val(); - IETR_VAL(keyUnion, union2(existingKey, rightKeyType.raw())); - zval *existingValue = arrayIndex(existingUnsealed.raw(), 1); - if (UNEXPECTED(existingValue == NULL)) return zv::Val(); - IETR_VAL(valueUnion, union2(existingValue, rightValueType.raw())); - if (UNEXPECTED(!pt_constant_array_type_builder_make_unsealed(newArrayBuilder.raw(), keyUnion.raw(), valueUnion.raw()))) return zv::Val(); - } else { - if (UNEXPECTED(!pt_constant_array_type_builder_make_unsealed(newArrayBuilder.raw(), rightKeyType.raw(), rightValueType.raw()))) return zv::Val(); - } - IETR_VAL(array, pt_constant_array_type_builder_get_array(newArrayBuilder.raw())); - resultTypes.push(std::move(array)); - } - return unionOf(resultTypes); - } - } - } - - IETR_TRI(leftIsArray, triOn(leftType, mIsArray)); - IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); - if (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_YES) { - IETR_VAL(leftKeyTypeForEquals, callOn(leftType, mGetIterableKeyType)); - IETR_VAL(rightKeyTypeForEquals, callOn(rightType, mGetIterableKeyType)); - zv::Val keyType; - { - zv::Val equalsHold = callOn(leftKeyTypeForEquals.raw(), mEquals, 1, rightKeyTypeForEquals.raw()); - if (UNEXPECTED(equalsHold.isUndef())) return zv::Val(); - if (zend_is_true(equalsHold.raw())) { - // to preserve BenevolentUnionType - keyType = callOn(leftType, mGetIterableKeyType); - } else { - IETR_VAL(leftKey, callOn(leftType, mGetIterableKeyType)); - IETR_VAL(rightKey, callOn(rightType, mGetIterableKeyType)); - keyType = union2(leftKey.raw(), rightKey.raw()); - } - if (UNEXPECTED(keyType.isUndef())) return zv::Val(); - } - - IETR_VAL(leftIterableValueType, callOn(leftType, mGetIterableValueType)); - IETR_VAL(rightIterableValueType, callOn(rightType, mGetIterableValueType)); - IETR_VAL(valueUnion, union2(leftIterableValueType.raw(), rightIterableValueType.raw())); - IETR_VAL(arrayType, newArrayType(keyType.raw(), valueUnion.raw())); - - zv::Arr accessories = zv::Arr::create(4); - if (leftCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - zval *constantArray = arrayIndex(leftConstantArrays.raw(), 0); - if (UNEXPECTED(constantArray == NULL)) return zv::Val(); - IETR_VAL(keyTypes, callOn(constantArray, mGetKeyTypes)); - if (UNEXPECTED(!requireArray(keyTypes.raw()))) return zv::Val(); - for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { - zval *offsetType = keyEntry.value().raw(); - IETR_TRI(hasOffset, triOn(leftType, mHasOffsetValueType, 1, offsetType)); - if (hasOffset != PT_TRI_YES) { - continue; - } - - IETR_VAL(valueType, callOn(leftType, mGetOffsetValueType, 1, offsetType)); - zval accessory; - if (UNEXPECTED(!pt_has_offset_value_type_new(&accessory, offsetType, valueType.raw()))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - } - - if (rightCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - zval *constantArray = arrayIndex(rightConstantArrays.raw(), 0); - if (UNEXPECTED(constantArray == NULL)) return zv::Val(); - IETR_VAL(keyTypes, callOn(constantArray, mGetKeyTypes)); - if (UNEXPECTED(!requireArray(keyTypes.raw()))) return zv::Val(); - for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { - zval *offsetType = keyEntry.value().raw(); - IETR_TRI(hasOffset, triOn(rightType, mHasOffsetValueType, 1, offsetType)); - if (hasOffset != PT_TRI_YES) { - continue; - } - - IETR_VAL(rightOffsetValueType, callOn(rightType, mGetOffsetValueType, 1, offsetType)); - IETR_VAL(valueType, union2(leftIterableValueType.raw(), rightOffsetValueType.raw())); - zval accessory; - if (UNEXPECTED(!pt_has_offset_value_type_new(&accessory, offsetType, valueType.raw()))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - } - - IETR_TRI(leftAtLeastOnce, triOn(leftType, mIsIterableAtLeastOnce)); - bool nonEmpty = leftAtLeastOnce == PT_TRI_YES; - if (!nonEmpty) { - IETR_TRI(rightAtLeastOnce, triOn(rightType, mIsIterableAtLeastOnce)); - nonEmpty = rightAtLeastOnce == PT_TRI_YES; - } - if (nonEmpty) { - zval accessory; - if (UNEXPECTED(!pt_non_empty_array_type_new(&accessory))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - IETR_TRI(leftIsList, triOn(leftType, mIsList)); - if (leftIsList == PT_TRI_YES) { - IETR_TRI(rightIsList, triOn(rightType, mIsList)); - if (rightIsList == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_array_list_type_new(&accessory))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - } - - uint32_t accessoryCount = zend_hash_num_elements(accessories.table()); - if (accessoryCount > 0) { - zv::Arr intersectArgs = zv::Arr::create(accessoryCount + 1); - intersectArgs.push(std::move(arrayType)); - for (zv::ArrayEntry entry : zv::ArrRef(accessories.raw())) { - intersectArgs.push(entry.value()); - } - return pt_type_combinator_intersect(accessoryCount + 1, intersectArgs.table()->arPacked); - } - - return arrayType; - } - - bool leftMixed = isA(leftType, pt_ce_mixed_type); - bool rightMixed = isA(rightType, pt_ce_mixed_type); - if (leftMixed && rightMixed) { - IETR_VAL(floatType, newFloatType()); - IETR_VAL(integerType, newIntegerType()); - zv::Arr types = zv::Arr::create(3); - types.push(std::move(floatType)); - types.push(std::move(integerType)); - if (leftIsArray == PT_TRI_NO && rightIsArray == PT_TRI_NO) { - return newBenevolentUnionType(types); - } - IETR_VAL(keyMixed, newMixedType()); - IETR_VAL(valueMixed, newMixedType()); - IETR_VAL(arrayType, newArrayType(keyMixed.raw(), valueMixed.raw())); - types.push(std::move(arrayType)); - return newBenevolentUnionType(types); - } - - if ( - (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_NO) - || (leftIsArray == PT_TRI_NO && rightIsArray == PT_TRI_YES) - ) { - return newErrorType(); - } - - if ( - (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_MAYBE) - || (leftIsArray == PT_TRI_MAYBE && rightIsArray == PT_TRI_YES) - ) { - IETR_VAL(keyMixed, newMixedType()); - IETR_VAL(valueMixed, newMixedType()); - IETR_VAL(resultType, newArrayType(keyMixed.raw(), valueMixed.raw())); - IETR_TRI(leftAtLeastOnce, triOn(leftType, mIsIterableAtLeastOnce)); - bool nonEmpty = leftAtLeastOnce == PT_TRI_YES; - if (!nonEmpty) { - IETR_TRI(rightAtLeastOnce, triOn(rightType, mIsIterableAtLeastOnce)); - nonEmpty = rightAtLeastOnce == PT_TRI_YES; - } - if (nonEmpty) { - zval accessory; - if (UNEXPECTED(!pt_non_empty_array_type_new(&accessory))) return zv::Val(); - zv::Val accessoryHold = zv::Val::adopt(accessory); - zv::Args argv{resultType.raw(), accessoryHold.raw()}; - return pt_type_combinator_intersect(2, argv); - } - - return resultType; - } - - if (leftIsArray == PT_TRI_MAYBE && rightIsArray == PT_TRI_MAYBE) { - zv::Arr plusableTypes = zv::Arr::create(5); - { - IETR_VAL(stringType, newStringType()); - plusableTypes.push(std::move(stringType)); - IETR_VAL(floatType, newFloatType()); - plusableTypes.push(std::move(floatType)); - IETR_VAL(integerType, newIntegerType()); - plusableTypes.push(std::move(integerType)); - IETR_VAL(keyMixed, newMixedType()); - IETR_VAL(valueMixed, newMixedType()); - IETR_VAL(arrayType, newArrayType(keyMixed.raw(), valueMixed.raw())); - plusableTypes.push(std::move(arrayType)); - IETR_VAL(booleanType, newBooleanType()); - plusableTypes.push(std::move(booleanType)); - } - IETR_VAL(plusable, newUnionType(plusableTypes)); - - IETR_TRI(plusableSuperTypeOfLeftTri, triOn(plusable.raw(), mIsSuperTypeOf, 1, leftType)); - IETR_TRI(plusableSuperTypeOfRightTri, triOn(plusable.raw(), mIsSuperTypeOf, 1, rightType)); - bool plusableSuperTypeOfLeft = plusableSuperTypeOfLeftTri == PT_TRI_YES; - bool plusableSuperTypeOfRight = plusableSuperTypeOfRightTri == PT_TRI_YES; - if (plusableSuperTypeOfLeft && plusableSuperTypeOfRight) { - return union2(leftType, rightType); - } - if (plusableSuperTypeOfLeft && rightMixed) { - return zv::Val::copyOf(zv::Ref(leftType)); - } - if (plusableSuperTypeOfRight && leftMixed) { - return zv::Val::copyOf(zv::Ref(rightType)); - } - } - - return resolveCommonMath(IETR_PLUS, left, right, NULL, leftType, rightType); - } - - /* Mirrors getMinusType() / getMulType() (kind IETR_MINUS / IETR_MUL). */ - zv::Val getMinusOrMulType(IetrBinaryKind kind, zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftTypeHold, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightTypeHold, getTypeOf(getTypeCallback, right)); - zval *leftType = leftTypeHold.raw(); - zval *rightType = rightTypeHold.raw(); - - zv::Val result; - int constant = constantScalarArithmetic(leftType, rightType, kind, result); - if (UNEXPECTED(constant < 0)) return zv::Val(); - if (constant == 0) return result; - - zv::Val optimizedLeft; - zv::Val optimizedRight; - if (constant == 1) { - optimizedLeft = optimizeScalarType(leftType); - if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); - leftType = optimizedLeft.raw(); - optimizedRight = optimizeScalarType(rightType); - if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); - rightType = optimizedRight.raw(); - } - - if (kind == IETR_MUL) { - { - IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); - int zero = isConstantIntegerZero(leftNumberType.raw()); - if (UNEXPECTED(zero < 0)) return zv::Val(); - if (zero) { - IETR_TRI(rightIsFloat, triOn(rightType, mIsFloat)); - if (rightIsFloat == PT_TRI_YES) { - return newConstantFloatType(0.0); - } - return newConstantIntegerType(0); - } - } - { - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - int zero = isConstantIntegerZero(rightNumberType.raw()); - if (UNEXPECTED(zero < 0)) return zv::Val(); - if (zero) { - IETR_TRI(leftIsFloat, triOn(leftType, mIsFloat)); - if (leftIsFloat == PT_TRI_YES) { - return newConstantFloatType(0.0); - } - return newConstantIntegerType(0); - } - } - } - - return resolveCommonMath(kind, left, right, NULL, leftType, rightType); - } - - /* Mirrors getPowType(). */ - zv::Val getPowType(zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftType, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightType, getTypeOf(getTypeCallback, right)); - - { - IETR_VAL(node, newBinaryOpNode(PT_CLASS_POW_EXPR, left, right)); - IETR_VAL(extensionSpecified, callOperatorTypeSpecifyingExtensions(prop(slots::operatorTypeSpecifyingExtensionRegistry), node.raw(), leftType.raw(), rightType.raw())); - if (Z_TYPE_P(extensionSpecified.raw()) != IS_NULL) return extensionSpecified; - } - - IETR_VAL(exponentiatedTyped, callOn(leftType.raw(), mExponentiate, 1, rightType.raw())); - if (!isA(exponentiatedTyped.raw(), pt_ce_error_type)) { - return exponentiatedTyped; - } - - return newErrorType(); - } - - /* Mirrors getShiftLeftType() / getShiftRightType(). */ - zv::Val getShiftType(IetrBinaryKind kind, zval *left, zval *right, const pt_ietr_get_type &getTypeCallback) const - { - IETR_VAL(leftTypeHold, getTypeOf(getTypeCallback, left)); - IETR_VAL(rightTypeHold, getTypeOf(getTypeCallback, right)); - zval *leftType = leftTypeHold.raw(); - zval *rightType = rightTypeHold.raw(); - - { - IETR_VAL(node, newBinaryOpNode(ietrBinaryKindClasses[kind], left, right)); - IETR_VAL(specifiedTypes, callOperatorTypeSpecifyingExtensions(prop(slots::operatorTypeSpecifyingExtensionRegistry), node.raw(), leftType, rightType)); - if (Z_TYPE_P(specifiedTypes.raw()) != IS_NULL) return specifiedTypes; - } - - if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { - return getNeverType(leftType, rightType); - } - - zv::Val result; - int constant = constantScalarArithmetic(leftType, rightType, kind, result); - if (UNEXPECTED(constant < 0)) return zv::Val(); - if (constant == 0) return result; - - zv::Val optimizedLeft; - zv::Val optimizedRight; - if (constant == 1) { - optimizedLeft = optimizeScalarType(leftType); - if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); - leftType = optimizedLeft.raw(); - optimizedRight = optimizeScalarType(rightType); - if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); - rightType = optimizedRight.raw(); - } - - { - IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - - if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { - return newErrorType(); - } - } - - return resolveCommonMath(kind, left, right, NULL, leftType, rightType); - } - - /* Mirrors optimizeScalarType(). */ - static zv::Val optimizeScalarType(zval *type) - { - zv::Arr types = zv::Arr::create(4); - { - IETR_TRI(isInteger, triOn(type, mIsInteger)); - if (isInteger == PT_TRI_YES) { - IETR_VAL(integerType, newIntegerType()); - types.push(std::move(integerType)); - } - } - { - IETR_TRI(isString, triOn(type, mIsString)); - if (isString == PT_TRI_YES) { - IETR_VAL(stringType, newStringType()); - types.push(std::move(stringType)); - } - } - { - IETR_TRI(isFloat, triOn(type, mIsFloat)); - if (isFloat == PT_TRI_YES) { - IETR_VAL(floatType, newFloatType()); - types.push(std::move(floatType)); - } - } - { - IETR_TRI(isNull, triOn(type, mIsNull)); - if (isNull == PT_TRI_YES) { - IETR_VAL(nullType, newNullType()); - types.push(std::move(nullType)); - } - } - - uint32_t count = zend_hash_num_elements(types.table()); - if (count == 0) { - return newErrorType(); - } - - if (count == 1) { - return zv::Val::copyOf(zv::Ref(zend_hash_index_find(types.table(), 0))); - } - - return newUnionType(types); - } - - /* Mirrors getNonNegativeIntegerBounds(): true fills min/max (max null - * 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; - } - - /* Mirrors getNonNegativeIntegerBounds() as the PHP value it returns: - * [min, max] or null */ - static zv::Val getNonNegativeIntegerBoundsValue(zval *type) - { - zend_long min = 0; - NullableLong max = NullableLong::null(); - int bounds = getNonNegativeIntegerBounds(type, min, max); - if (UNEXPECTED(bounds < 0)) return zv::Val(); - if (bounds == 0) return zv::Val::null(); - zv::Arr pair = zv::Arr::create(2); - pair.push(zv::Val::integer(min)); - pair.push(max.toVal()); - return zv::Val(std::move(pair)); - } - - /* Mirrors computeBitwiseAndRange(): a Type, or PHP null */ - static zv::Val computeBitwiseAndRange(zval *leftNumberType, zval *rightNumberType) - { - zend_long leftMin = 0, rightMin = 0; - NullableLong leftMax = NullableLong::null(), rightMax = NullableLong::null(); - int leftBounds = getNonNegativeIntegerBounds(leftNumberType, leftMin, leftMax); - if (UNEXPECTED(leftBounds < 0)) return zv::Val(); - int rightBounds = getNonNegativeIntegerBounds(rightNumberType, rightMin, rightMax); - if (UNEXPECTED(rightBounds < 0)) return zv::Val(); - if (leftBounds == 0 && rightBounds == 0) { - return zv::Val::null(); - } - - bool anyMax = false; - zend_long minOfMax = 0; - if (leftBounds == 1 && !leftMax.isNull) { - minOfMax = leftMax.value; - anyMax = true; - } - if (rightBounds == 1 && !rightMax.isNull) { - minOfMax = anyMax && minOfMax <= rightMax.value ? minOfMax : rightMax.value; - anyMax = true; - } - - return fromInterval(NullableLong::of(0), anyMax ? NullableLong::of(minOfMax) : NullableLong::null()); - } - - /* Mirrors computeBitwiseOrXorRange(): a Type, or PHP null */ - static zv::Val computeBitwiseOrXorRange(zval *leftNumberType, zval *rightNumberType) - { - zend_long leftMin = 0, rightMin = 0; - NullableLong leftMax = NullableLong::null(), rightMax = NullableLong::null(); - int leftBounds = getNonNegativeIntegerBounds(leftNumberType, leftMin, leftMax); - if (UNEXPECTED(leftBounds < 0)) return zv::Val(); - int rightBounds = getNonNegativeIntegerBounds(rightNumberType, rightMin, rightMax); - if (UNEXPECTED(rightBounds < 0)) return zv::Val(); - if (leftBounds == 0 || rightBounds == 0) { - return zv::Val::null(); - } - if (leftMax.isNull || rightMax.isNull) { - return fromInterval(NullableLong::of(0), NullableLong::null()); + return getNeverType(leftTypes.raw(), rightTypes.raw()); } - return fromInterval(NullableLong::of(0), NullableLong::of(allBitsMask(leftMax.value >= rightMax.value ? leftMax.value : rightMax.value))); - } + IETR_VAL(leftValues, callOn(leftTypes.raw(), mGetConstantScalarValues)); + IETR_VAL(rightValues, callOn(rightTypes.raw(), mGetConstantScalarValues)); + if (UNEXPECTED(!requireArray(leftValues.raw()) || !requireArray(rightValues.raw()))) return zv::Val(); - /* Mirrors allBitsMask(). */ - static zend_long allBitsMask(zend_long value) - { - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value |= value >> 32; + zend_long leftValuesCount = zend_hash_num_elements(Z_ARRVAL_P(leftValues.raw())); + zend_long rightValuesCount = zend_hash_num_elements(Z_ARRVAL_P(rightValues.raw())); + if (leftValuesCount > 0 && rightValuesCount > 0 && leftValuesCount * rightValuesCount <= PT_IETR_CALCULATE_SCALARS_LIMIT) { + zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftValuesCount * rightValuesCount)); + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftValues.raw())) { + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightValues.raw())) { + /* LooseComparisonHelper::compareValues($leftValue, $rightValue, $this->phpVersion) ?? 1 */ + zv::Args args{leftEntry.value().raw(), rightEntry.value().raw(), prop(slots::phpVersion)}; + zv::Val compared = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("comparevalues"), 3, args); + if (UNEXPECTED(compared.isUndef())) return zv::Val(); + zval comparison; + ZVAL_LONG(&comparison, Z_TYPE_P(compared.raw()) == IS_LONG ? Z_LVAL_P(compared.raw()) : 1); + IETR_VAL(resultType, typeFromValue(&comparison)); + resultTypes.push(std::move(resultType)); + } + } + return unionOf(resultTypes); + } - return value; + return fromInterval(NullableLong::of(-1), NullableLong::of(1)); } /* }}} */ @@ -2659,446 +927,6 @@ class InitializerExprTypeResolver /* }}} */ - /* {{{ resolveCommonMath() / integerRangeMath() */ - - /* Mirrors resolveCommonMath(); the twin's $expr is `new BinaryOp\($left, - * $right)` created here (or the node itself when one is given) */ - zv::Val resolveCommonMath(IetrBinaryKind kind, zval *left, zval *right, zval *givenExpr, zval *leftType, zval *rightType) const - { - { - zv::Val exprHold; - zval *expr = givenExpr; - if (expr == NULL) { - exprHold = newBinaryOpNode(ietrBinaryKindClasses[kind], left, right); - if (UNEXPECTED(exprHold.isUndef())) return zv::Val(); - expr = exprHold.raw(); - } - IETR_VAL(specifiedTypes, callOperatorTypeSpecifyingExtensions(prop(slots::operatorTypeSpecifyingExtensionRegistry), expr, leftType, rightType)); - if (Z_TYPE_P(specifiedTypes.raw()) != IS_NULL) return specifiedTypes; - } - - 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); - } - - return callOn(union_.raw(), mToNumber); - } - } - - { - IETR_TRI(leftIsArray, triOn(leftType, mIsArray)); - if (leftIsArray == PT_TRI_YES) return newErrorType(); - IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); - if (rightIsArray == PT_TRI_YES) return newErrorType(); - IETR_TRI(typesIsArray, triOn(types.raw(), mIsArray)); - if (typesIsArray == PT_TRI_YES) return newErrorType(); - } - - if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { - return newErrorType(); - } - if (isA(leftNumberType.raw(), pt_ce_never_type) || isA(rightNumberType.raw(), pt_ce_never_type)) { - return getNeverType(leftNumberType.raw(), rightNumberType.raw()); - } - - { - IETR_TRI(leftIsFloat, triOn(leftNumberType.raw(), mIsFloat)); - bool isFloat = leftIsFloat == PT_TRI_YES; - if (!isFloat) { - IETR_TRI(rightIsFloat, triOn(rightNumberType.raw(), mIsFloat)); - isFloat = rightIsFloat == PT_TRI_YES; - } - if (isFloat) { - if (kind == IETR_SHIFT_LEFT || kind == IETR_SHIFT_RIGHT) { - return newIntegerType(); - } - return newFloatType(); - } - } - - IETR_VAL(resultType, union2(leftNumberType.raw(), rightNumberType.raw())); - if (kind == IETR_DIV) { - bool benevolent = typesMixed; - if (!benevolent) { - IETR_TRI(resultIsInteger, triOn(resultType.raw(), mIsInteger)); - benevolent = resultIsInteger == PT_TRI_YES; - } - return newIntFloatUnion(benevolent); - } - - if (typesMixed - || isA(leftType, pt_ce_benevolent_union_type) - || isA(rightType, pt_ce_benevolent_union_type) - ) { - return toBenevolentUnion(resultType.raw()); - } - - return resultType; - } - - /* Mirrors integerRangeMath() ($range a ConstantIntegerType or - * IntegerRangeType, $node's class as `kind`). */ - zv::Val integerRangeMath(zval *range, IetrBinaryKind kind, zval *operandIn) const - { - 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: - if (operandIsConstant) { - if (Z_TYPE(rangeMin) != IS_NULL && UNEXPECTED(!scalarOp(sub_function, &rangeMin, operandValue, &min))) return zv::Val(); - if (Z_TYPE(rangeMax) != IS_NULL && UNEXPECTED(!scalarOp(sub_function, &rangeMax, operandValue, &max))) return zv::Val(); - } else { - if (identical(&rangeMin, &rangeMax) && Z_TYPE(rangeMin) != IS_NULL - && (Z_TYPE(operandMin) == IS_NULL || Z_TYPE(operandMax) == IS_NULL)) { - ZVAL_NULL(&min); - ZVAL_COPY_VALUE(&max, &rangeMin); - } else { - if (Z_TYPE(operandMin) == IS_NULL) { - ZVAL_NULL(&min); - } else if (Z_TYPE(rangeMin) != IS_NULL) { - if (Z_TYPE(operandMax) != IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); - } else { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMin, &min))) return zv::Val(); - } - } else { - ZVAL_NULL(&min); - } - - if (Z_TYPE(operandMax) == IS_NULL) { - ZVAL_NULL(&min); - ZVAL_NULL(&max); - } else if (Z_TYPE(rangeMax) != IS_NULL) { - if (Z_TYPE(rangeMin) != IS_NULL && Z_TYPE(operandMin) == IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); - ZVAL_NULL(&max); - } else if (Z_TYPE(operandMin) != IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMax, &operandMin, &max))) return zv::Val(); - } else { - ZVAL_NULL(&max); - } - } else { - 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); - } - } - } - 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 */ - zv::Val splitDivision(zval *fixed, IetrBinaryKind kind, zval *negative, zval *positive, bool operandSplit) const - { - 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; - } - - /* }}} */ - /* {{{ the unary operators */ /* Mirrors getUnaryPlusType(). */ @@ -3124,57 +952,7 @@ class InitializerExprTypeResolver return specifiedTypes; } - IETR_VAL(negated, getUnaryMinusTypeFromType(expr, type.raw())); - if (isA(negated.raw(), pt_ce_integer_range_type)) { - zval minusOne; - ZVAL_LONG(&minusOne, -1); - IETR_VAL(minusOneNode, pt_type_new(PT_CLASS_SCALAR_INT, 1, &minusOne)); - IETR_VAL(mulNode, newBinaryOpNode(PT_CLASS_MUL_EXPR, expr, minusOneNode.raw())); - return getTypeOf(getTypeCallback, mulNode.raw()); - } - - return negated; - } - - /* Mirrors getUnaryMinusTypeFromType(). */ - static zv::Val getUnaryMinusTypeFromType(zval *expr, zval *typeIn) - { - (void) expr; - IETR_VAL(type, callOn(typeIn, mToNumber)); - IETR_VAL(scalarValues, callOn(type.raw(), mGetConstantScalarValues)); - if (UNEXPECTED(!requireCountable(scalarValues.raw()))) return zv::Val(); - - uint32_t count = zend_hash_num_elements(Z_ARRVAL_P(scalarValues.raw())); - if (count > 0) { - zv::Arr newTypes = zv::Arr::create(count); - for (zv::ArrayEntry entry : zv::ArrRef(scalarValues.raw())) { - zval *scalarValue = entry.value().raw(); - ZVAL_DEREF(scalarValue); - if (Z_TYPE_P(scalarValue) == IS_LONG) { - zval minusOne, newValue; - ZVAL_LONG(&minusOne, -1); - if (UNEXPECTED(!scalarOp(mul_function, scalarValue, &minusOne, &newValue))) return zv::Val(); - if (Z_TYPE(newValue) != IS_LONG) { - // Negating the smallest integer overflows into a float. - IETR_VAL(floatType, newConstantFloatType(zval_get_double(&newValue))); - newTypes.push(std::move(floatType)); - continue; - } - IETR_VAL(integerType, newConstantIntegerType(Z_LVAL(newValue))); - newTypes.push(std::move(integerType)); - } else if (Z_TYPE_P(scalarValue) == IS_DOUBLE) { - zval minusOne, newValue; - ZVAL_LONG(&minusOne, -1); - if (UNEXPECTED(!scalarOp(mul_function, scalarValue, &minusOne, &newValue))) return zv::Val(); - IETR_VAL(floatType, newConstantFloatType(Z_DVAL(newValue))); - newTypes.push(std::move(floatType)); - } - } - - return unionOf(newTypes); - } - - return type; + return callOn(type.raw(), mArithmeticNegate, 1, prop(slots::phpVersion)); } /* Mirrors getBitwiseNotType(). */ @@ -3217,12 +995,6 @@ class InitializerExprTypeResolver return newNeverType(); } - /* Mirrors getTypeFromValue(). */ - static zv::Val getTypeFromValue(zval *value) - { - return typeFromValue(value); - } - /* }}} */ /* {{{ getType() */ @@ -3453,11 +1225,11 @@ class InitializerExprTypeResolver case ARM_CONCAT: return getConcatType(binaryLeft(expr), binaryRight(expr), callback); case ARM_BITWISE_AND: - return getBitwiseType(IETR_BITWISE_AND, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_BITWISE_AND, binaryLeft(expr), binaryRight(expr), callback); case ARM_BITWISE_OR: - return getBitwiseType(IETR_BITWISE_OR, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_BITWISE_OR, binaryLeft(expr), binaryRight(expr), callback); case ARM_BITWISE_XOR: - return getBitwiseType(IETR_BITWISE_XOR, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_BITWISE_XOR, binaryLeft(expr), binaryRight(expr), callback); case ARM_SPACESHIP: return getSpaceshipType(binaryLeft(expr), binaryRight(expr), callback); case ARM_BOOLEAN_AND: @@ -3468,19 +1240,19 @@ class InitializerExprTypeResolver case ARM_DIV: return getDivType(binaryLeft(expr), binaryRight(expr), callback); case ARM_MOD: - return getModType(binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_MOD, binaryLeft(expr), binaryRight(expr), callback); case ARM_PLUS: - return getPlusType(binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_PLUS, binaryLeft(expr), binaryRight(expr), callback); case ARM_MINUS: - return getMinusOrMulType(IETR_MINUS, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_MINUS, binaryLeft(expr), binaryRight(expr), callback); case ARM_MUL: - return getMinusOrMulType(IETR_MUL, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_MUL, binaryLeft(expr), binaryRight(expr), callback); case ARM_POW: - return getPowType(binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_POW, binaryLeft(expr), binaryRight(expr), callback); case ARM_SHIFT_LEFT: - return getShiftType(IETR_SHIFT_LEFT, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_SHIFT_LEFT, binaryLeft(expr), binaryRight(expr), callback); case ARM_SHIFT_RIGHT: - return getShiftType(IETR_SHIFT_RIGHT, binaryLeft(expr), binaryRight(expr), callback); + return getOperatorType(IETR_SHIFT_RIGHT, binaryLeft(expr), binaryRight(expr), callback); case ARM_IDENTICAL: case ARM_EQUAL: { IETR_VAL(leftType, getTypeRecursive(binaryLeft(expr), context)); @@ -4959,37 +2731,6 @@ class InitializerExprTypeResolver /* {{{ value helpers */ - /* count() / foreach over a Type method's array result: the engine's - * TypeError / warning for anything else; false = pending exception */ - static bool requireArray(zval *value) - { - if (EXPECTED(Z_TYPE_P(value) == IS_ARRAY)) return true; - zend_type_error("count(): Argument #1 ($value) must be of type Countable|array, %s given", zend_zval_value_name(value)); - return false; - } - - static bool requireCountable(zval *value) - { - return requireArray(value); - } - - /* $array[$index] of a list the twin indexes ($leftConstantArrays[0], - * $existingUnsealed[0]); NULL with the engine's warning + Error... the - * twin reads only indexes it knows exist, so a miss is a broken - * collaborator: ShouldNotHappenException */ - static zval *arrayIndex(zval *array, zend_ulong index) - { - if (EXPECTED(Z_TYPE_P(array) == IS_ARRAY)) { - zval *value = zend_hash_index_find(Z_ARRVAL_P(array), index); - if (EXPECTED(value != NULL)) { - ZVAL_DEREF(value); - return value; - } - } - pt_throw_should_not_happen(); - return NULL; - } - /* $array[$index] read for a value the twin then calls a method on */ static zval *arrayIndexForRead(zval *array, zend_ulong index) { @@ -5013,17 +2754,6 @@ class InitializerExprTypeResolver return value; } - /* the foreach key of an entry as a PHP value (borrowed string) */ - static void entryKey(const zv::ArrayEntry &entry, zval &key) - { - zend_string *stringKey = entry.stringKeyOrNull(); - if (stringKey != NULL) { - ZVAL_STR(&key, stringKey); - } else { - ZVAL_LONG(&key, (zend_long) entry.indexKey()); - } - } - /* array_merge($a, $b) of two lists of reasons */ static zv::Val arrayMerge(zval *a, zval *b) { @@ -5044,100 +2774,6 @@ class InitializerExprTypeResolver return zv::Val(std::move(merged)); } - /* $type instanceof ConstantStringType && $type->getValue() === ''; - * -1 = pending exception */ - static int isEmptyConstantString(zval *type) - { - if (!isA(type, pt_ce_constant_string_type)) return 0; - return constantStringValueIsEmpty(type); - } - - /* $constantString->getValue() === '' of a ConstantStringType; -1 = - * pending exception */ - static int constantStringValueIsEmpty(zval *constantString) - { - zv::Val value = constantStringValue(constantString); - if (UNEXPECTED(value.isUndef())) return -1; - return Z_TYPE_P(value.raw()) == IS_STRING && ZSTR_LEN(Z_STR_P(value.raw())) == 0 ? 1 : 0; - } - - /* $constantString->getValue() */ - static zv::Val constantStringValue(zval *constantString) - { - if (EXPECTED(isA(constantString, pt_ce_constant_string_type))) return pt_constant_string_get_value(Z_OBJ_P(constantString)); - return callOn(constantString, mGetValue); - } - - /* Strings::match($value, '#^\d+$#') !== null: ASCII digits, a final - * newline allowed before the end ($ without D) */ - static bool isDecimalDigits(zend_string *value) - { - size_t length = ZSTR_LEN(value); - if (length > 0 && ZSTR_VAL(value)[length - 1] == '\n') length--; - if (length == 0) return false; - for (size_t i = 0; i < length; i++) { - char c = ZSTR_VAL(value)[i]; - if (c < '0' || c > '9') return false; - } - return true; - } - - /* $type instanceof ConstantIntegerType && $type->getValue() === 0; -1 = - * pending exception */ - static int isConstantIntegerZero(zval *type) - { - if (!isA(type, pt_ce_constant_integer_type)) return 0; - zend_long value; - if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(type), value))) return -1; - return value == 0 ? 1 : 0; - } - - /* $result = $a $b for scalar operands (no release needed); false = - * pending exception */ - /* 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; - } - - static bool scalarOp(pt_ietr_binary_op fn, zval *a, zval *b, zval *result) - { - ZVAL_UNDEF(result); - if (UNEXPECTED(fn(result, a, b) != SUCCESS || EG(exception) != NULL)) { - zval_ptr_dtor(result); - ZVAL_UNDEF(result); - return false; - } - return true; - } - - /* `$a === 0 || $b === 0 ? 0 : ($a ?? $aDefault) * ($b ?? $bDefault)` */ - static bool mulOrZero(zval *a, zval *b, zval *aDefault, zval *bDefault, zval *result) - { - if ((Z_TYPE_P(a) == IS_LONG && Z_LVAL_P(a) == 0) || (Z_TYPE_P(b) == IS_LONG && Z_LVAL_P(b) == 0)) { - ZVAL_LONG(result, 0); - return true; - } - return scalarOp(mul_function, Z_TYPE_P(a) != IS_NULL ? a : aDefault, Z_TYPE_P(b) != IS_NULL ? b : bDefault, result); - } - - static void setNullableLong(zval *out, NullableLong value) - { - if (value.isNull) { - ZVAL_NULL(out); - } else { - ZVAL_LONG(out, value.value); - } - } - /* $typeResult->type */ static zv::Val typeResultType(zval *result) { @@ -5395,7 +3031,6 @@ class InitializerExprTypeResolver } // namespace phpstanturbo using phpstanturbo::InitializerExprTypeResolver; -using phpstanturbo::IetrBinaryKind; /* {{{ direct entries */ @@ -5466,29 +3101,29 @@ zv::Val pt_initializer_expr_type_resolver_get_binary_op_type(zval *resolver, pt_ case PT_IETR_OP_CONCAT: return native.getConcatType(left, right, getTypeCallback); case PT_IETR_OP_BITWISE_AND: - return native.getBitwiseType(phpstanturbo::IETR_BITWISE_AND, left, right, getTypeCallback); + return native.getOperatorType(IETR_BITWISE_AND, left, right, getTypeCallback); case PT_IETR_OP_BITWISE_OR: - return native.getBitwiseType(phpstanturbo::IETR_BITWISE_OR, left, right, getTypeCallback); + return native.getOperatorType(IETR_BITWISE_OR, left, right, getTypeCallback); case PT_IETR_OP_BITWISE_XOR: - return native.getBitwiseType(phpstanturbo::IETR_BITWISE_XOR, left, right, getTypeCallback); + return native.getOperatorType(IETR_BITWISE_XOR, left, right, getTypeCallback); case PT_IETR_OP_SPACESHIP: return native.getSpaceshipType(left, right, getTypeCallback); case PT_IETR_OP_DIV: return native.getDivType(left, right, getTypeCallback); case PT_IETR_OP_MOD: - return native.getModType(left, right, getTypeCallback); + return native.getOperatorType(IETR_MOD, left, right, getTypeCallback); case PT_IETR_OP_PLUS: - return native.getPlusType(left, right, getTypeCallback); + return native.getOperatorType(IETR_PLUS, left, right, getTypeCallback); case PT_IETR_OP_MINUS: - return native.getMinusOrMulType(phpstanturbo::IETR_MINUS, left, right, getTypeCallback); + return native.getOperatorType(IETR_MINUS, left, right, getTypeCallback); case PT_IETR_OP_MUL: - return native.getMinusOrMulType(phpstanturbo::IETR_MUL, left, right, getTypeCallback); + return native.getOperatorType(IETR_MUL, left, right, getTypeCallback); case PT_IETR_OP_POW: - return native.getPowType(left, right, getTypeCallback); + return native.getOperatorType(IETR_POW, left, right, getTypeCallback); case PT_IETR_OP_SHIFT_LEFT: - return native.getShiftType(phpstanturbo::IETR_SHIFT_LEFT, left, right, getTypeCallback); + return native.getOperatorType(IETR_SHIFT_LEFT, left, right, getTypeCallback); case PT_IETR_OP_SHIFT_RIGHT: - return native.getShiftType(phpstanturbo::IETR_SHIFT_RIGHT, left, right, getTypeCallback); + return native.getOperatorType(IETR_SHIFT_RIGHT, left, right, getTypeCallback); } pt_throw_should_not_happen(); return zv::Val(); @@ -5674,29 +3309,6 @@ bool ietrArgCallable(zval *value, uint32_t arg) return false; } -/* the node class of a BinaryOp handed to the private math methods */ -IetrBinaryKind binaryKindOf(zval *node, bool &ok) -{ - ok = true; - static constexpr phpstanturbo::IetrBinaryKind kinds[] = { - phpstanturbo::IETR_PLUS, - phpstanturbo::IETR_MINUS, - phpstanturbo::IETR_MUL, - phpstanturbo::IETR_DIV, - phpstanturbo::IETR_SHIFT_LEFT, - phpstanturbo::IETR_SHIFT_RIGHT, - }; - for (phpstanturbo::IetrBinaryKind kind : kinds) { - int matches = isAClass(node, phpstanturbo::ietrBinaryKindClasses[kind]); - if (UNEXPECTED(matches < 0)) { - ok = false; - return phpstanturbo::IETR_OTHER; - } - if (matches) return kind; - } - return phpstanturbo::IETR_OTHER; -} - /* the (Expr $left, Expr $right, callable $getTypeCallback) glue */ #define IETR_OPERATOR_GLUE(body) \ [](INTERNAL_FUNCTION_PARAMETERS) { \ @@ -5726,8 +3338,6 @@ PT_MINIT_REGISTRATION(pt_register_initializer_expr_type_resolver) reg::Class cls("PHPStan\\Reflection\\InitializerExprTypeResolver"); ptdecl::InitializerExprTypeResolver::declareClass(cls); cls.classConstantLong("CALCULATE_SCALARS_LIMIT", PT_INITIALIZER_EXPR_TYPE_RESOLVER_CALCULATE_SCALARS_LIMIT); - cls.privateClassConstantLong("IS_SCALAR_TYPE", PT_IETR_IS_SCALAR_TYPE); - cls.privateClassConstantLong("IS_UNKNOWN", PT_IETR_IS_UNKNOWN); ptdecl::InitializerExprTypeResolver::declareProperties(cls); /* the real parameter class names: the DI container autowires the @@ -5835,106 +3445,20 @@ PT_MINIT_REGISTRATION(pt_register_initializer_expr_type_resolver) PT_RETURN_VAL(InitializerExprTypeResolver::createFirstClassCallable(function, variants, nativeTypesPromoted)); }); - cls.method(sigs::getBitwiseAndType, IETR_OPERATOR_GLUE(self.getBitwiseType(phpstanturbo::IETR_BITWISE_AND, left, right, getTypeCallback))); - cls.method(sigs::getBitwiseOrType, IETR_OPERATOR_GLUE(self.getBitwiseType(phpstanturbo::IETR_BITWISE_OR, left, right, getTypeCallback))); - cls.method(sigs::getBitwiseXorType, IETR_OPERATOR_GLUE(self.getBitwiseType(phpstanturbo::IETR_BITWISE_XOR, left, right, getTypeCallback))); - - cls.method(sigs::getFiniteOrConstantScalarTypes, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *leftType, *rightType, *operationCallable; - if (!zp::parse(execute_data, leftType, rightType, operationCallable)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(leftType, 1) || !ietrArgType(rightType, 2) || !ietrArgCallable(operationCallable, 3))) RETURN_THROWS(); - zv::Val result; - zend_long code = InitializerExprTypeResolver::getFiniteOrConstantScalarTypesWith(leftType, rightType, NULL, operationCallable, result); - if (UNEXPECTED(code < 0)) RETURN_THROWS(); - if (code == 0) PT_RETURN_VAL(std::move(result)); - RETURN_LONG(code); - }); + cls.method(sigs::getBitwiseAndType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_BITWISE_AND, left, right, getTypeCallback))); + cls.method(sigs::getBitwiseOrType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_BITWISE_OR, left, right, getTypeCallback))); + cls.method(sigs::getBitwiseXorType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_BITWISE_XOR, left, right, getTypeCallback))); cls.method(sigs::getSpaceshipType, IETR_OPERATOR_GLUE(self.getSpaceshipType(left, right, getTypeCallback))); cls.method(sigs::getDivType, IETR_OPERATOR_GLUE(self.getDivType(left, right, getTypeCallback))); - cls.method(sigs::getDivTypeFromTypes, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *left, *right, *leftType, *rightType; - if (!zp::parse(execute_data, left, right, leftType, rightType)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgExpr(left, 1) || !ietrArgExpr(right, 2) || !ietrArgType(leftType, 3) || !ietrArgType(rightType, 4))) RETURN_THROWS(); - PT_RETURN_VAL(IETR_THIS.getDivTypeFromTypes(left, right, leftType, rightType)); - }); - - cls.method(sigs::getModType, IETR_OPERATOR_GLUE(self.getModType(left, right, getTypeCallback))); - cls.method(sigs::getPlusType, IETR_OPERATOR_GLUE(self.getPlusType(left, right, getTypeCallback))); - cls.method(sigs::getMinusType, IETR_OPERATOR_GLUE(self.getMinusOrMulType(phpstanturbo::IETR_MINUS, left, right, getTypeCallback))); - cls.method(sigs::getMulType, IETR_OPERATOR_GLUE(self.getMinusOrMulType(phpstanturbo::IETR_MUL, left, right, getTypeCallback))); - cls.method(sigs::getPowType, IETR_OPERATOR_GLUE(self.getPowType(left, right, getTypeCallback))); - cls.method(sigs::getShiftLeftType, IETR_OPERATOR_GLUE(self.getShiftType(phpstanturbo::IETR_SHIFT_LEFT, left, right, getTypeCallback))); - cls.method(sigs::getShiftRightType, IETR_OPERATOR_GLUE(self.getShiftType(phpstanturbo::IETR_SHIFT_RIGHT, left, right, getTypeCallback))); - - cls.method(sigs::optimizeScalarType, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *type; - if (!zp::parse(execute_data, type)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(type, 1))) RETURN_THROWS(); - PT_RETURN_VAL(InitializerExprTypeResolver::optimizeScalarType(type)); - }); - - cls.method(sigs::getNonNegativeIntegerBounds, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *type; - if (!zp::parse(execute_data, type)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(type, 1))) RETURN_THROWS(); - PT_RETURN_VAL(InitializerExprTypeResolver::getNonNegativeIntegerBoundsValue(type)); - }); - - cls.method(sigs::getMaxModuloMagnitude, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_long divisorMin, divisorMax; - bool divisorMinIsNull, divisorMaxIsNull; - ZEND_PARSE_PARAMETERS_START(2, 2) - Z_PARAM_LONG_OR_NULL(divisorMin, divisorMinIsNull) - Z_PARAM_LONG_OR_NULL(divisorMax, divisorMaxIsNull) - ZEND_PARSE_PARAMETERS_END(); - NullableLong magnitude = InitializerExprTypeResolver::getMaxModuloMagnitude( - divisorMinIsNull ? NullableLong::null() : NullableLong::of(divisorMin), - divisorMaxIsNull ? NullableLong::null() : NullableLong::of(divisorMax)); - PT_RETURN_VAL(magnitude.toVal()); - }); - - cls.method(sigs::getIntegerBounds, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *type; - if (!zp::parse(execute_data, type)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(type, 1))) RETURN_THROWS(); - PT_RETURN_VAL(IETR_THIS.getIntegerBoundsValue(type)); - }); - - 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(!InitializerExprTypeResolver::shiftLeftOverflowsValue(value, shift, out))) RETURN_THROWS(); - RETURN_BOOL(out); - }); - - cls.method(sigs::toIntBound, [](INTERNAL_FUNCTION_PARAMETERS) { - double value; - if (!zp::parse(execute_data, value)) RETURN_THROWS(); - InitializerExprTypeResolver::toIntBound(value, return_value); - }); - - cls.method(sigs::computeBitwiseAndRange, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *leftNumberType, *rightNumberType; - if (!zp::parse(execute_data, leftNumberType, rightNumberType)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(leftNumberType, 1) || !ietrArgType(rightNumberType, 2))) RETURN_THROWS(); - PT_RETURN_VAL(InitializerExprTypeResolver::computeBitwiseAndRange(leftNumberType, rightNumberType)); - }); - - cls.method(sigs::computeBitwiseOrXorRange, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *leftNumberType, *rightNumberType; - if (!zp::parse(execute_data, leftNumberType, rightNumberType)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(leftNumberType, 1) || !ietrArgType(rightNumberType, 2))) RETURN_THROWS(); - PT_RETURN_VAL(InitializerExprTypeResolver::computeBitwiseOrXorRange(leftNumberType, rightNumberType)); - }); - - cls.method(sigs::allBitsMask, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_long value; - if (!zp::parse(execute_data, value)) RETURN_THROWS(); - RETURN_LONG(InitializerExprTypeResolver::allBitsMask(value)); - }); + cls.method(sigs::getModType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_MOD, left, right, getTypeCallback))); + cls.method(sigs::getPlusType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_PLUS, left, right, getTypeCallback))); + cls.method(sigs::getMinusType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_MINUS, left, right, getTypeCallback))); + cls.method(sigs::getMulType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_MUL, left, right, getTypeCallback))); + cls.method(sigs::getPowType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_POW, left, right, getTypeCallback))); + cls.method(sigs::getShiftLeftType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_SHIFT_LEFT, left, right, getTypeCallback))); + cls.method(sigs::getShiftRightType, IETR_OPERATOR_GLUE(self.getOperatorType(IETR_SHIFT_RIGHT, left, right, getTypeCallback))); cls.method(sigs::resolveIdenticalType, [](INTERNAL_FUNCTION_PARAMETERS) { zval *leftType, *rightType; @@ -5961,26 +3485,6 @@ PT_MINIT_REGISTRATION(pt_register_initializer_expr_type_resolver) PT_RETURN_VAL(IETR_THIS.resolveConstantArrayTypeComparison(leftType, rightType, true, valueComparisonCallback)); }); - cls.method(sigs::resolveCommonMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *expr, *leftType, *rightType; - if (!zp::parse(execute_data, expr, leftType, rightType)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgClass(expr, 1, PT_CLASS_BINARY_OP_EXPR, "PhpParser\\Node\\Expr\\BinaryOp") || !ietrArgType(leftType, 2) || !ietrArgType(rightType, 3))) RETURN_THROWS(); - bool ok; - IetrBinaryKind kind = binaryKindOf(expr, ok); - if (UNEXPECTED(!ok)) RETURN_THROWS(); - PT_RETURN_VAL(IETR_THIS.resolveCommonMath(kind, NULL, NULL, expr, leftType, rightType)); - }); - - cls.method(sigs::integerRangeMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *range, *node, *operand; - if (!zp::parse(execute_data, range, node, operand)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgType(range, 1) || !ietrArgClass(node, 2, PT_CLASS_BINARY_OP_EXPR, "PhpParser\\Node\\Expr\\BinaryOp") || !ietrArgType(operand, 3))) RETURN_THROWS(); - bool ok; - IetrBinaryKind kind = binaryKindOf(node, ok); - if (UNEXPECTED(!ok)) RETURN_THROWS(); - PT_RETURN_VAL(IETR_THIS.integerRangeMath(range, kind, operand)); - }); - cls.method(sigs::getClassConstFetchTypeByReflection, [](INTERNAL_FUNCTION_PARAMETERS) { zval *class_, *classReflection, *callable; zend_string *constantName; @@ -6019,13 +3523,6 @@ PT_MINIT_REGISTRATION(pt_register_initializer_expr_type_resolver) cls.method(sigs::getUnaryPlusType, IETR_UNARY_GLUE(PT_CLASS_EXPR, "PhpParser\\Node\\Expr", self.getUnaryPlusType(expr, getTypeCallback))); cls.method(sigs::getUnaryMinusType, IETR_UNARY_GLUE(PT_CLASS_EXPR, "PhpParser\\Node\\Expr", self.getUnaryMinusType(expr, getTypeCallback))); - cls.method(sigs::getUnaryMinusTypeFromType, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *expr, *type; - if (!zp::parse(execute_data, expr, type)) RETURN_THROWS(); - if (UNEXPECTED(!ietrArgExpr(expr, 1) || !ietrArgType(type, 2))) RETURN_THROWS(); - PT_RETURN_VAL(InitializerExprTypeResolver::getUnaryMinusTypeFromType(expr, type)); - }); - cls.method(sigs::getBitwiseNotType, IETR_UNARY_GLUE(PT_CLASS_EXPR, "PhpParser\\Node\\Expr", self.getBitwiseNotType(expr, getTypeCallback))); cls.method(sigs::getBitwiseNotTypeFromType, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -6066,12 +3563,6 @@ PT_MINIT_REGISTRATION(pt_register_initializer_expr_type_resolver) PT_RETURN_VAL(InitializerExprTypeResolver::resolveTypeByNameWithLateStaticBinding(class_, classType, methodReflectionCandidate)); }); - cls.method(sigs::getTypeFromValue, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *value; - if (!zp::parse(execute_data, value)) RETURN_THROWS(); - PT_RETURN_VAL(InitializerExprTypeResolver::getTypeFromValue(value)); - }); - cls.method(sigs::getReflectionProvider, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); PT_RETURN_VAL(IETR_THIS.getReflectionProvider()); diff --git a/turbo-ext/src/IntegerRangeType.cpp b/turbo-ext/src/IntegerRangeType.cpp index 1afa62f7565..1d41c34f696 100644 --- a/turbo-ext/src/IntegerRangeType.cpp +++ b/turbo-ext/src/IntegerRangeType.cpp @@ -842,7 +842,7 @@ class IntegerRangeType * an IntegerRangeType exponent's bounds or a constant int exponent * when every power stays an int; ExponentiateHelper otherwise; UNDEF = * pending exception */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { bool isUnion; if (UNEXPECTED(!pt_union_type_instanceof(exponent, isUnion))) return zv::Val(); @@ -860,8 +860,9 @@ class IntegerRangeType zend_type_error("phpstan_turbo: UnionType::getTypes() must return %s instances", ZSTR_VAL(pt_ce_integer_type->name)); return zv::Val(); } - /* $this->exponentiate($unionType) */ - zv::Val result = isExact() ? exponentiate(unionType) : pt_type_call(self, PT_LC("exponentiate"), 1, unionType); + /* $this->arithmeticPower($unionType, $phpVersion) */ + zv::Args args{unionType, phpVersion}; + zv::Val result = isExact() ? arithmeticPower(unionType, phpVersion) : pt_type_call(self, PT_LC("arithmeticpower"), 2, args); if (UNEXPECTED(result.isUndef())) return zv::Val(); results.push(std::move(result)); } @@ -913,8 +914,8 @@ class IntegerRangeType } } - /* parent::exponentiate($exponent) */ - return pt_integer_type_exponentiate(self, exponent); + /* parent::arithmeticPower($exponent, $phpVersion) */ + return pt_integer_type_arithmetic_power(self, exponent, phpVersion); } /* every integer of a bounded range as a ConstantIntegerType, unless @@ -1496,7 +1497,7 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) pt_irt_try(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntegerRangeType::tryRemove); }); - cls.method<&IntegerRangeType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&IntegerRangeType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method<&IntegerRangeType::getFiniteTypes>(sigs::getFiniteTypes); @@ -1504,6 +1505,7 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) cls.method<&IntegerRangeType::looseCompare, zp::Obj, zp::Zval>(sigs::looseCompare); + pt_type_trait_operator(cls); cls.shadow(&pt_ce_integer_range_type); } diff --git a/turbo-ext/src/IntegerType.cpp b/turbo-ext/src/IntegerType.cpp index 40270d4f313..3114077faa5 100644 --- a/turbo-ext/src/IntegerType.cpp +++ b/turbo-ext/src/IntegerType.cpp @@ -185,8 +185,9 @@ class IntegerType static zv::Val getFiniteTypes() { return zv::Val(zv::Arr::empty()); } /* ExponentiateHelper::exponentiate($this, $exponent) */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; zv::Args args{self, exponent}; return pt_type_call_static(PT_CLASS_EXPONENTIATE_HELPER, PT_LC("exponentiate"), 2, args); } @@ -232,9 +233,9 @@ zv::Val pt_integer_type_loose_compare(zend_object *self, zval *type, zval *phpVe return IntegerType::looseCompare(type, phpVersion); } -zv::Val pt_integer_type_exponentiate(zend_object *self, zval *exponent) +zv::Val pt_integer_type_arithmetic_power(zend_object *self, zval *exponent, zval *phpVersion) { - return IntegerType(self).exponentiate(exponent); + return IntegerType(self).arithmeticPower(exponent, phpVersion); } /* {{{ engine ABI glue: parameter parsing + registration */ @@ -329,7 +330,7 @@ PT_MINIT_REGISTRATION(pt_register_integer_type) cls.method<&IntegerType::getFiniteTypes>(sigs::getFiniteTypes); - cls.method<&IntegerType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&IntegerType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method<&IntegerType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/IntersectionType.cpp b/turbo-ext/src/IntersectionType.cpp index 043ef370f6d..1f47b0d8b75 100644 --- a/turbo-ext/src/IntersectionType.cpp +++ b/turbo-ext/src/IntersectionType.cpp @@ -2139,7 +2139,11 @@ class IntersectionType }); } - zv::Val exponentiate(zval *exponent) const { return intersectCall(PT_LC("exponentiate"), 1, exponent); } + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const + { + zv::Args args{exponent, phpVersion}; + return intersectCall(PT_LC("arithmeticpower"), 2, args); + } /* the finite types every member has, keyed by enum case or type-only * description, [] beyond CALCULATE_SCALARS_LIMIT */ @@ -3697,8 +3701,8 @@ PT_MINIT_REGISTRATION(pt_register_intersection_type) cls.method(sigs::tryRemove, [](INTERNAL_FUNCTION_PARAMETERS) { pt_it_value_object(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntersectionType::tryRemove); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - pt_it_value_object(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntersectionType::exponentiate); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + pt_it_value_two_objects(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntersectionType::arithmeticPower); }); cls.method(sigs::getFiniteTypes, [](INTERNAL_FUNCTION_PARAMETERS) { pt_it_value0(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntersectionType::getFiniteTypes); diff --git a/turbo-ext/src/IterableType.cpp b/turbo-ext/src/IterableType.cpp index 35d78e0e5f4..1037b265bb5 100644 --- a/turbo-ext/src/IterableType.cpp +++ b/turbo-ext/src/IterableType.cpp @@ -1013,8 +1013,8 @@ PT_MINIT_REGISTRATION(pt_register_iterable_type) cls.method<&IterableType::tryRemove, zp::Obj>(sigs::tryRemove); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); }); cls.method(sigs::getFiniteTypes, itEmptyArray0); diff --git a/turbo-ext/src/MixedType.cpp b/turbo-ext/src/MixedType.cpp index a0591fb4a87..58c2816dc1c 100644 --- a/turbo-ext/src/MixedType.cpp +++ b/turbo-ext/src/MixedType.cpp @@ -1010,7 +1010,7 @@ class MixedType } /* new BenevolentUnionType([new FloatType(), new IntegerType()]) */ - static zv::Val exponentiate() + static zv::Val arithmeticPower() { zv::Arr types = zv::Arr::create(2); if (UNEXPECTED(!pushNew(types, pt_float_type_new) || !pushInteger(types))) return zv::Val(); @@ -1882,9 +1882,9 @@ PT_MINIT_REGISTRATION(pt_register_mixed_type) cls.method<&MixedType::tryRemove, zp::Obj>(sigs::tryRemove); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); - PT_RETURN_VAL(MixedType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + PT_RETURN_VAL(MixedType::arithmeticPower()); }); cls.method(sigs::getFiniteTypes, mtEmptyArray0); diff --git a/turbo-ext/src/NeverType.cpp b/turbo-ext/src/NeverType.cpp index 929ec96278a..ab3adfeb9aa 100644 --- a/turbo-ext/src/NeverType.cpp +++ b/turbo-ext/src/NeverType.cpp @@ -495,7 +495,10 @@ PT_MINIT_REGISTRATION(pt_register_never_type) ZEND_PARSE_PARAMETERS_NONE(); RETURN_NULL(); }); - cls.method(sigs::exponentiate, ntThis1); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + PT_ARGS(2, 2); + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); + }); cls.method(sigs::getFiniteTypes, ntEmptyArray0); cls.method<&NeverType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/NonEmptyArrayType.cpp b/turbo-ext/src/NonEmptyArrayType.cpp index 7f9f4e0eab1..27dffdf638a 100644 --- a/turbo-ext/src/NonEmptyArrayType.cpp +++ b/turbo-ext/src/NonEmptyArrayType.cpp @@ -483,7 +483,7 @@ PT_MINIT_REGISTRATION(pt_register_non_empty_array_type) cls.method("traverse", reg::Public, 1, { reg::callableArg("cb") }, pt_type_identity_traverse_handler(), &ptret::type); cls.op(PT_OP_TRAVERSE, PT_OP_LAMBDA { return pt_op_traverse_identity(self); }); cls.method(sigs::traverseSimultaneously, neaThis2); - cls.method(sigs::exponentiate, neaError1); + cls.method(sigs::arithmeticPower, neaError2); cls.method(sigs::getFiniteTypes, neaEmptyArray0); cls.method<&NonEmptyArrayType::getDefaultBaseType>(sigs::getDefaultBaseType); diff --git a/turbo-ext/src/NonexistentParentClassType.cpp b/turbo-ext/src/NonexistentParentClassType.cpp index bc7e27dcb12..840ecf9c399 100644 --- a/turbo-ext/src/NonexistentParentClassType.cpp +++ b/turbo-ext/src/NonexistentParentClassType.cpp @@ -195,7 +195,7 @@ PT_MINIT_REGISTRATION(pt_register_nonexistent_parent_class_type) RETURN_NULL(); }); cls.op(PT_OP_GET_ENUM_CASE_OBJECT, PT_OP_LAMBDA { return zv::Val::null(); }); - cls.method(sigs::exponentiate, npcError1); + cls.method(sigs::arithmeticPower, npcError2); cls.method(sigs::getFiniteTypes, npcEmptyArray0); cls.method(sigs::toPhpDocNode, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/NullType.cpp b/turbo-ext/src/NullType.cpp index 00aac8ef4bc..5dd13944056 100644 --- a/turbo-ext/src/NullType.cpp +++ b/turbo-ext/src/NullType.cpp @@ -305,7 +305,7 @@ class NullType zv::Val getFiniteTypes() const { return getConstantScalarTypes(); } /* new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(1)]) */ - static zv::Val exponentiate() + static zv::Val arithmeticPower() { zv::Val zero = pt_type_new_constant_integer(0); if (UNEXPECTED(zero.isUndef())) return zv::Val(); @@ -703,10 +703,10 @@ PT_MINIT_REGISTRATION(pt_register_null_type) cls.method<&NullType::getFiniteTypes>(sigs::getFiniteTypes); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *exponent; - if (!zp::parse(execute_data, exponent)) RETURN_THROWS(); - PT_RETURN_VAL(NullType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *exponent, *phpVersion; + if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(NullType::arithmeticPower()); }); cls.method<&NullType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/ObjectShapeType.cpp b/turbo-ext/src/ObjectShapeType.cpp index 171af1935f5..cc4cfb0d15a 100644 --- a/turbo-ext/src/ObjectShapeType.cpp +++ b/turbo-ext/src/ObjectShapeType.cpp @@ -682,8 +682,9 @@ class ObjectShapeType /* TypeCombinator::union($this, $exponent) unless the exponent is never * or no subtype of $this, float|int (benevolent) otherwise; UNDEF = * pending exception */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; if (!instanceof_function(Z_OBJCE_P(exponent), pt_ce_never_type)) { zv::Val isSuperType = isExact() ? isSuperTypeOf(exponent) : pt_type_op(self, PT_OP_IS_SUPER_TYPE_OF, 1, exponent); if (UNEXPECTED(isSuperType.isUndef())) return zv::Val(); @@ -1366,7 +1367,7 @@ PT_MINIT_REGISTRATION(pt_register_object_shape_type) PT_RETURN_VAL(PT_THIS.traverseSimultaneously(right, &fci, &fcc)); }); - cls.method<&ObjectShapeType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&ObjectShapeType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method(sigs::getFiniteTypes, ostEmptyArray0); @@ -1394,6 +1395,7 @@ PT_MINIT_REGISTRATION(pt_register_object_shape_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("isobjectallowed"), PT_CLASS_IDENTIFIER, PT_LC("object"))); }); + pt_type_trait_operator(cls); cls.shadow(&pt_ce_object_shape_type); } diff --git a/turbo-ext/src/ObjectType.cpp b/turbo-ext/src/ObjectType.cpp index 37c204f98ed..90357537f03 100644 --- a/turbo-ext/src/ObjectType.cpp +++ b/turbo-ext/src/ObjectType.cpp @@ -2713,7 +2713,7 @@ class ObjectType /* }}} */ - /* {{{ tryRemove(), exponentiate(), toPhpDocNode(), hasTemplateOrLateResolvableType() */ + /* {{{ tryRemove(), arithmeticPower(), toPhpDocNode(), hasTemplateOrLateResolvableType() */ /* the rest of an EQUAL_UNION_CLASSES base class when one of its classes * is removed, $this->subtract() for a subtype, null otherwise; UNDEF = @@ -2828,8 +2828,9 @@ class ObjectType /* $this|$exponent when both may be objects, ErrorType otherwise; UNDEF * = pending exception */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; zv::Val object = pt_type_new_object_without_class_type(); if (UNEXPECTED(object.isUndef())) return zv::Val(); if (!zv::Ref(exponent).instanceOf(pt_ce_never_type)) { @@ -3929,8 +3930,10 @@ PT_MINIT_REGISTRATION(pt_register_object_type) cls.method(sigs::getFiniteTypes, [](INTERNAL_FUNCTION_PARAMETERS) { pt_ot_value(INTERNAL_FUNCTION_PARAM_PASSTHRU, &ObjectType::getFiniteTypes); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - pt_ot_value_of_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, &ObjectType::exponentiate); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *exponent, *phpVersion; + if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(PT_THIS.arithmeticPower(exponent, phpVersion)); }); cls.method(sigs::toPhpDocNode, [](INTERNAL_FUNCTION_PARAMETERS) { pt_ot_value(INTERNAL_FUNCTION_PARAM_PASSTHRU, &ObjectType::toPhpDocNode); diff --git a/turbo-ext/src/ObjectWithoutClassType.cpp b/turbo-ext/src/ObjectWithoutClassType.cpp index bc62543d403..1663cedb5ea 100644 --- a/turbo-ext/src/ObjectWithoutClassType.cpp +++ b/turbo-ext/src/ObjectWithoutClassType.cpp @@ -286,8 +286,9 @@ class ObjectWithoutClassType /* TypeCombinator::union($this, $exponent) unless the exponent is never * or no subtype of $this, float|int (benevolent) otherwise; UNDEF = * pending exception */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; if (!instanceof_function(Z_OBJCE_P(exponent), pt_ce_never_type)) { zend_long isSuperType = thisIsSuperTypeOf(exponent); if (UNEXPECTED(isSuperType < 0)) return zv::Val(); @@ -434,7 +435,7 @@ PT_MINIT_REGISTRATION(pt_register_object_without_class_type) cls.method<&ObjectWithoutClassType::tryRemove, zp::Obj>(sigs::tryRemove); - cls.method<&ObjectWithoutClassType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&ObjectWithoutClassType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method(sigs::getFiniteTypes, owctEmptyArray0); @@ -468,6 +469,7 @@ PT_MINIT_REGISTRATION(pt_register_object_without_class_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("isobjectallowed"), PT_CLASS_IDENTIFIER, PT_LC("object"))); }); + pt_type_trait_operator(cls); cls.shadow(&pt_ce_object_without_class_type); } diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp new file mode 100644 index 00000000000..2f3c5333706 --- /dev/null +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -0,0 +1,2362 @@ +/* + * PHPStanTurbo\OperatorTypeHelper — native implementation of + * PHPStan\Type\OperatorTypeHelper, the operators of Type::arithmetic*(), + * Type::bitwise*(), Type::stringConcatenate() and Type::arrayUnion() that + * OperatorTypeTrait delegates to (pt_type_trait_operator() in + * TypeTraits.cpp calls pt_operator_type_helper_binary() / + * pt_operator_type_helper_negate() directly). + * + * A final class of static methods: every method mirrors its twin, the + * private ones are registered too so the method sets stay identical. The + * arithmetic runs on the engine's own operator functions (add_function(), + * mod_function(), bitwise_and_function(), zend_compare(), ...), so int + * overflow into float, float-to-int truncation and the mixed-type + * comparisons behave exactly like the twin's PHP operators. None of the + * operators depends on the PhpVersion yet; the twin's parameter is accepted + * and passed along. + */ + +#include "support.h" +#include "generated/OperatorTypeHelper.h" + +#include "zv.h" +#include "IetrSupport.h" +#include "reg.h" + +#include +#include +#include + +zend_class_entry *pt_ce_operator_type_helper = nullptr; + +namespace sigs = ptdecl::OperatorTypeHelper::sig; + +using namespace phpstanturbo::ietr; + +namespace phpstanturbo { + +/* Mirrors PHPStan\Type\OperatorTypeHelper; UNDEF = pending exception. */ +class OperatorTypeHelper +{ +public: + + /* {{{ concat() */ + + /* Mirrors concat(). */ + static zv::Val concat(zval *left, zval *right) + { + IETR_VAL(leftStringType, callOn(left, mToString)); + IETR_VAL(rightStringType, callOn(right, mToString)); + { + IETR_VAL(union_, union2(leftStringType.raw(), rightStringType.raw())); + if (isA(union_.raw(), pt_ce_error_type)) return newErrorType(); + } + + int leftEmpty = isEmptyConstantString(leftStringType.raw()); + if (UNEXPECTED(leftEmpty < 0)) return zv::Val(); + if (leftEmpty) return rightStringType; + + int rightEmpty = isEmptyConstantString(rightStringType.raw()); + if (UNEXPECTED(rightEmpty < 0)) return zv::Val(); + if (rightEmpty) return leftStringType; + + if (isA(leftStringType.raw(), pt_ce_constant_string_type) && isA(rightStringType.raw(), pt_ce_constant_string_type)) { + return callOn(leftStringType.raw(), mAppend, 1, rightStringType.raw()); + } + + IETR_VAL(leftConstantStrings, callOn(leftStringType.raw(), mGetConstantStrings)); + IETR_VAL(rightConstantStrings, callOn(rightStringType.raw(), mGetConstantStrings)); + if (UNEXPECTED(!requireArray(leftConstantStrings.raw()) || !requireArray(rightConstantStrings.raw()))) return zv::Val(); + zend_long combinedConstantStringsCount = (zend_long) zend_hash_num_elements(Z_ARRVAL_P(leftConstantStrings.raw())) * (zend_long) zend_hash_num_elements(Z_ARRVAL_P(rightConstantStrings.raw())); + + // we limit the number of union-types for performance reasons + if (combinedConstantStringsCount > 0 && combinedConstantStringsCount <= PT_IETR_CALCULATE_SCALARS_LIMIT) { + zv::Arr strings = zv::Arr::create((uint32_t) combinedConstantStringsCount); + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantStrings.raw())) { + zval *leftConstantString = leftEntry.value().raw(); + int leftStringEmpty = constantStringValueIsEmpty(leftConstantString); + if (UNEXPECTED(leftStringEmpty < 0)) return zv::Val(); + if (leftStringEmpty) { + // $strings = array_merge($strings, $rightConstantStrings) + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantStrings.raw())) { + if (rightEntry.hasStringKey()) { + strings.set(rightEntry.stringKey(), zv::Val::copyOf(rightEntry.value())); + } else { + strings.push(rightEntry.value()); + } + } + continue; + } + + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantStrings.raw())) { + zval *rightConstantString = rightEntry.value().raw(); + int rightStringEmpty = constantStringValueIsEmpty(rightConstantString); + if (UNEXPECTED(rightStringEmpty < 0)) return zv::Val(); + if (rightStringEmpty) { + strings.push(zv::Ref(leftConstantString)); + continue; + } + + IETR_VAL(appended, callOn(leftConstantString, mAppend, 1, rightConstantString)); + strings.push(std::move(appended)); + } + } + + if (zend_hash_num_elements(strings.table()) > 0) { + return unionOf(strings); + } + } + + zv::Arr accessoryTypes = zv::Arr::create(6); + { + IETR_TRI(leftNonEmpty, triOn(leftStringType.raw(), mIsNonEmptyString)); + IETR_TRI(rightNonEmpty, triOn(rightStringType.raw(), mIsNonEmptyString)); + if (pt_trinary_and(leftNonEmpty, rightNonEmpty) == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_non_falsy_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } else { + IETR_TRI(leftNonFalsy, triOn(leftStringType.raw(), mIsNonFalsyString)); + IETR_TRI(rightNonFalsy, triOn(rightStringType.raw(), mIsNonFalsyString)); + if (pt_trinary_or(leftNonFalsy, rightNonFalsy) == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_non_falsy_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } else { + IETR_TRI(leftNonEmpty2, triOn(leftStringType.raw(), mIsNonEmptyString)); + IETR_TRI(rightNonEmpty2, triOn(rightStringType.raw(), mIsNonEmptyString)); + if (pt_trinary_or(leftNonEmpty2, rightNonEmpty2) == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_non_empty_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } + } + } + } + + { + IETR_TRI(leftLiteral, triOn(leftStringType.raw(), mIsLiteralString)); + IETR_TRI(rightLiteral, triOn(rightStringType.raw(), mIsLiteralString)); + if (pt_trinary_and(leftLiteral, rightLiteral) == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_literal_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } + } + + { + IETR_TRI(leftLowercase, triOn(leftStringType.raw(), mIsLowercaseString)); + IETR_TRI(rightLowercase, triOn(rightStringType.raw(), mIsLowercaseString)); + if (pt_trinary_and(leftLowercase, rightLowercase) == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_lowercase_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } + } + + { + IETR_TRI(leftUppercase, triOn(leftStringType.raw(), mIsUppercaseString)); + IETR_TRI(rightUppercase, triOn(rightStringType.raw(), mIsUppercaseString)); + if (pt_trinary_and(leftUppercase, rightUppercase) == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_uppercase_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } + } + + { + zval emptyString; + ZVAL_EMPTY_STRING(&emptyString); + IETR_VAL(emptyStringType, newConstantStringType(Z_STR(emptyString))); + IETR_VAL(leftNumericStringNonEmpty, pt_type_combinator_remove(leftStringType.raw(), emptyStringType.raw())); + IETR_TRI(leftNumeric, triOn(leftNumericStringNonEmpty.raw(), mIsNumericString)); + if (leftNumeric == PT_TRI_YES) { + IETR_TRI(leftIsInteger, triOn(left, mIsInteger)); + bool integerValidation = leftIsInteger == PT_TRI_YES; + + bool allRightConstantsZeroOrMore = false; + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantStrings.raw())) { + zval *rightConstantString = rightEntry.value().raw(); + IETR_VAL(value, constantStringValue(rightConstantString)); + if (UNEXPECTED(Z_TYPE_P(value.raw()) != IS_STRING)) { + // ConstantStringType::getValue(): string + pt_throw_should_not_happen(); + return zv::Val(); + } + zend_string *string = Z_STR_P(value.raw()); + if (ZSTR_LEN(string) == 0) { + continue; + } + + if ( + is_numeric_string(ZSTR_VAL(string), ZSTR_LEN(string), NULL, NULL, false) == 0 + || !(integerValidation ? ZSTR_VAL(string)[0] != '-' : isDecimalDigits(string)) + ) { + allRightConstantsZeroOrMore = false; + break; + } + + allRightConstantsZeroOrMore = true; + } + + bool nonNegativeRight = allRightConstantsZeroOrMore; + if (!nonNegativeRight) { + IETR_VAL(zeroOrMoreInteger, fromInterval(NullableLong::of(0), NullableLong::null())); + IETR_TRI(zeroOrMoreRight, triOn(zeroOrMoreInteger.raw(), mIsSuperTypeOf, 1, right)); + nonNegativeRight = zeroOrMoreRight == PT_TRI_YES; + } + if (nonNegativeRight) { + zval accessory; + if (UNEXPECTED(!pt_accessory_numeric_string_type_new(&accessory))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(accessory)); + } + } + } + + if (zend_hash_num_elements(accessoryTypes.table()) > 0) { + IETR_VAL(stringType, newStringType()); + accessoryTypes.push(std::move(stringType)); + return newIntersectionType(accessoryTypes); + } + + return newStringType(); + } + + /* }}} */ + + /* {{{ the bitwise operators */ + + /* Mirrors bitwiseAnd() / bitwiseOr() / bitwiseXor() (kind: + * IETR_BITWISE_AND / _OR / _XOR — the three bodies differ only in the + * operation and the range helper) */ + static zv::Val bitwise(IetrBinaryKind kind, zval *leftType, zval *rightType) + { + if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { + return getNeverType(leftType, rightType); + } + + zv::Val result; + zend_long code = getFiniteOrConstantScalarTypes(leftType, rightType, kind, result); + if (UNEXPECTED(code < 0)) return zv::Val(); + if (code == 0) return result; + + // computed before optimizeScalarType() below widens integer ranges to int + IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); + IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); + + zv::Val optimizedLeft; + zv::Val optimizedRight; + if (code == PT_IETR_IS_SCALAR_TYPE) { + optimizedLeft = optimizeScalarType(leftType); + if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); + leftType = optimizedLeft.raw(); + optimizedRight = optimizeScalarType(rightType); + if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); + rightType = optimizedRight.raw(); + } + + bool leftMixed = isA(leftType, pt_ce_mixed_type); + bool rightMixed = isA(rightType, pt_ce_mixed_type); + if (leftMixed && rightMixed) { + IETR_VAL(integerType, newIntegerType()); + IETR_VAL(stringType, newStringType()); + zv::Arr types = zv::Arr::create(2); + types.push(std::move(integerType)); + types.push(std::move(stringType)); + return newBenevolentUnionType(types); + } + + IETR_TRI(leftIsString, triOn(leftType, mIsString)); + IETR_TRI(rightIsString, triOn(rightType, mIsString)); + if ( + (leftIsString == PT_TRI_YES || leftMixed) + && (rightIsString == PT_TRI_YES || rightMixed) + ) { + return newStringType(); + } + if (leftIsString == PT_TRI_MAYBE && rightIsString == PT_TRI_MAYBE) { + return newErrorType(); + } + + { + IETR_VAL(leftToNumber, callOn(leftType, mToNumber)); + if (isA(leftToNumber.raw(), pt_ce_error_type)) return newErrorType(); + IETR_VAL(rightToNumber, callOn(rightType, mToNumber)); + if (isA(rightToNumber.raw(), pt_ce_error_type)) return newErrorType(); + } + + zv::Val range = kind == IETR_BITWISE_AND + ? computeBitwiseAndRange(leftNumberType.raw(), rightNumberType.raw()) + : computeBitwiseOrXorRange(leftNumberType.raw(), rightNumberType.raw()); + if (UNEXPECTED(range.isUndef())) return zv::Val(); + if (Z_TYPE_P(range.raw()) != IS_NULL) return range; + + return newIntegerType(); + } + + /* Mirrors getFiniteOrConstantScalarTypes() with the operation of `kind`: + * 0 = the Type in `result`, PT_IETR_IS_SCALAR_TYPE / PT_IETR_IS_UNKNOWN, + * -1 = pending exception */ + static zend_long getFiniteOrConstantScalarTypes(zval *leftType, zval *rightType, IetrBinaryKind kind, zv::Val &result) + { + pt_ietr_binary_op operation = kind == IETR_BITWISE_AND ? bitwise_and_function : (kind == IETR_BITWISE_OR ? bitwise_or_function : bitwise_xor_function); + return getFiniteOrConstantScalarTypesWith(leftType, rightType, operation, NULL, result); + } + + /* the same with the operation as the engine's operator function or, for + * a PHP caller of the private method, its $operationCallable */ + static zend_long getFiniteOrConstantScalarTypesWith(zval *leftType, zval *rightType, pt_ietr_binary_op operation, zval *operationCallable, zv::Val &result) + { + zv::Val leftTypes = isA(leftType, pt_ce_integer_range_type) ? callOn(leftType, mGetFiniteTypes) : callOn(leftType, mGetConstantScalarTypes); + if (UNEXPECTED(leftTypes.isUndef())) return -1; + zv::Val rightTypes = isA(rightType, pt_ce_integer_range_type) ? callOn(rightType, mGetFiniteTypes) : callOn(rightType, mGetConstantScalarTypes); + if (UNEXPECTED(rightTypes.isUndef())) return -1; + if (UNEXPECTED(!requireArray(leftTypes.raw()) || !requireArray(rightTypes.raw()))) return -1; + + zend_long leftTypesCount = zend_hash_num_elements(Z_ARRVAL_P(leftTypes.raw())); + zend_long rightTypesCount = zend_hash_num_elements(Z_ARRVAL_P(rightTypes.raw())); + + if (leftTypesCount == 0 || rightTypesCount == 0) { + return PT_IETR_IS_UNKNOWN; + } + + bool generalize = leftTypesCount * rightTypesCount > PT_IETR_CALCULATE_SCALARS_LIMIT; + if (generalize) { + return PT_IETR_IS_SCALAR_TYPE; + } + + zend_class_entry *constantScalarCe = pt_class(PT_CLASS_CONSTANT_SCALAR_TYPE); + if (UNEXPECTED(constantScalarCe == NULL)) return -1; + zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftTypesCount * rightTypesCount)); + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftTypes.raw())) { + zval *leftTypeInner = leftEntry.value().raw(); + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightTypes.raw())) { + zval *rightTypeInner = rightEntry.value().raw(); + zv::Val resultType; + if (isA(leftTypeInner, pt_ce_constant_string_type) && isA(rightTypeInner, pt_ce_constant_string_type)) { + zv::Val leftValue = callOn(leftTypeInner, mGetValue); + if (UNEXPECTED(leftValue.isUndef())) return -1; + zv::Val rightValue = callOn(rightTypeInner, mGetValue); + if (UNEXPECTED(rightValue.isUndef())) return -1; + zv::Val resultValue = applyOperation(operation, operationCallable, leftValue.raw(), rightValue.raw()); + if (UNEXPECTED(resultValue.isUndef())) return -1; + resultType = typeFromValue(resultValue.raw()); + } else { + zv::Val leftNumberType = callOn(leftTypeInner, mToNumber); + if (UNEXPECTED(leftNumberType.isUndef())) return -1; + zv::Val rightNumberType = callOn(rightTypeInner, mToNumber); + if (UNEXPECTED(rightNumberType.isUndef())) return -1; + + if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { + result = newErrorType(); + return result.isUndef() ? -1 : 0; + } + + if (!isA(leftNumberType.raw(), constantScalarCe) || !isA(rightNumberType.raw(), constantScalarCe)) { + pt_throw_should_not_happen(); + return -1; + } + + zv::Val leftValue = callOn(leftNumberType.raw(), mGetValue); + if (UNEXPECTED(leftValue.isUndef())) return -1; + zv::Val rightValue = callOn(rightNumberType.raw(), mGetValue); + if (UNEXPECTED(rightValue.isUndef())) return -1; + zv::Val resultValue = applyOperation(operation, operationCallable, leftValue.raw(), rightValue.raw()); + if (UNEXPECTED(resultValue.isUndef())) return -1; + resultType = typeFromValue(resultValue.raw()); + } + if (UNEXPECTED(resultType.isUndef())) return -1; + resultTypes.push(std::move(resultType)); + } + } + result = unionOf(resultTypes); + return result.isUndef() ? -1 : 0; + } + + /* $operationCallable($a, $b) or the operator */ + static zv::Val applyOperation(pt_ietr_binary_op operation, zval *operationCallable, zval *a, zval *b) + { + if (operationCallable != NULL) { + zv::Args argv{a, b}; + return pt_type_call_callable(operationCallable, 2, argv); + } + return phpOp(operation, a, b); + } + + /* }}} */ + + /* {{{ the arithmetic operators */ + + /* the constant-scalar product of add() / subtract() / multiply() / + * getDivTypeFromTypes() / modulo() / shiftLeft() / shiftRight(): 0 = the Type in `result` + * (the union, or an ErrorType the twin returns from inside the loop), + * 1 = generalize (the twin's optimizeScalarType() of both sides follows), + * 2 = not both sides constant, -1 = pending exception */ + static int constantScalarArithmetic(zval *leftType, zval *rightType, IetrBinaryKind kind, zv::Val &result) + { + zv::Val leftTypes = callOn(leftType, mGetConstantScalarTypes); + if (UNEXPECTED(leftTypes.isUndef())) return -1; + zv::Val rightTypes = callOn(rightType, mGetConstantScalarTypes); + if (UNEXPECTED(rightTypes.isUndef())) return -1; + if (UNEXPECTED(!requireArray(leftTypes.raw()) || !requireArray(rightTypes.raw()))) return -1; + zend_long leftTypesCount = zend_hash_num_elements(Z_ARRVAL_P(leftTypes.raw())); + zend_long rightTypesCount = zend_hash_num_elements(Z_ARRVAL_P(rightTypes.raw())); + if (leftTypesCount <= 0 || rightTypesCount <= 0) return 2; + + bool generalize = leftTypesCount * rightTypesCount > PT_IETR_CALCULATE_SCALARS_LIMIT; + if (generalize) return 1; + + zend_class_entry *constantScalarCe = pt_class(PT_CLASS_CONSTANT_SCALAR_TYPE); + if (UNEXPECTED(constantScalarCe == NULL)) return -1; + zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftTypesCount * rightTypesCount)); + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftTypes.raw())) { + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightTypes.raw())) { + zv::Val leftNumberType = callOn(leftEntry.value().raw(), mToNumber); + if (UNEXPECTED(leftNumberType.isUndef())) return -1; + zv::Val rightNumberType = callOn(rightEntry.value().raw(), mToNumber); + if (UNEXPECTED(rightNumberType.isUndef())) return -1; + + if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { + result = newErrorType(); + return result.isUndef() ? -1 : 0; + } + + if (!isA(leftNumberType.raw(), constantScalarCe) || !isA(rightNumberType.raw(), constantScalarCe)) { + pt_throw_should_not_happen(); + return -1; + } + + zv::Val leftValue = callOn(leftNumberType.raw(), mGetValue); + if (UNEXPECTED(leftValue.isUndef())) return -1; + zv::Val rightValue = callOn(rightNumberType.raw(), mGetValue); + if (UNEXPECTED(rightValue.isUndef())) return -1; + + zv::Val resultValue; + switch (kind) { + case IETR_PLUS: + resultValue = phpOp(add_function, leftValue.raw(), rightValue.raw()); + break; + case IETR_MINUS: + resultValue = phpOp(sub_function, leftValue.raw(), rightValue.raw()); + break; + case IETR_MUL: + resultValue = phpOp(mul_function, leftValue.raw(), rightValue.raw()); + break; + case IETR_DIV: + if (isZeroNumber(rightValue.raw())) { + result = newErrorType(); + return result.isUndef() ? -1 : 0; + } + resultValue = phpOp(div_function, leftValue.raw(), rightValue.raw()); + break; + case IETR_MOD: { + zval rightInteger; + ZVAL_LONG(&rightInteger, zval_get_long(rightValue.raw())); + if (Z_LVAL(rightInteger) == 0) { + result = newErrorType(); + return result.isUndef() ? -1 : 0; + } + zval leftInteger; + ZVAL_LONG(&leftInteger, zval_get_long(leftValue.raw())); + resultValue = phpOp(mod_function, &leftInteger, &rightInteger); + break; + } + case IETR_SHIFT_LEFT: + case IETR_SHIFT_RIGHT: { + zval zero; + ZVAL_LONG(&zero, 0); + if (lessThan(rightValue.raw(), &zero)) { + result = newErrorType(); + return result.isUndef() ? -1 : 0; + } + zval leftInteger, rightInteger; + ZVAL_LONG(&leftInteger, zval_get_long(leftValue.raw())); + ZVAL_LONG(&rightInteger, zval_get_long(rightValue.raw())); + resultValue = phpOp(kind == IETR_SHIFT_LEFT ? shift_left_function : shift_right_function, &leftInteger, &rightInteger); + break; + } + default: + pt_throw_should_not_happen(); + return -1; + } + if (UNEXPECTED(resultValue.isUndef())) return -1; + zv::Val resultType = typeFromValue(resultValue.raw()); + if (UNEXPECTED(resultType.isUndef())) return -1; + resultTypes.push(std::move(resultType)); + } + } + + result = unionOf(resultTypes); + return result.isUndef() ? -1 : 0; + } + + /* Mirrors divide(). */ + static zv::Val divide(zval *leftType, zval *rightType) + { + IETR_VAL(result, getDivTypeFromTypes(leftType, rightType)); + + IETR_TRI(leftIsInteger, triOn(leftType, mIsInteger)); + if (leftIsInteger == PT_TRI_YES) { + IETR_TRI(rightIsInteger, triOn(rightType, mIsInteger)); + if (rightIsInteger == PT_TRI_YES) { + IETR_VAL(modType, modulo(leftType, rightType)); + IETR_TRI(modIsInteger, triOn(modType.raw(), mIsInteger)); + if (modIsInteger == PT_TRI_YES) { + IETR_VAL(zero, newConstantIntegerType(0)); + IETR_TRI(zeroIsSuperTypeOfMod, triOn(zero.raw(), mIsSuperTypeOf, 1, modType.raw())); + if (zeroIsSuperTypeOfMod == PT_TRI_YES) { + IETR_VAL(floatType, newFloatType()); + IETR_VAL(withoutFloat, pt_type_combinator_remove(result.raw(), floatType.raw())); + + // PHP_INT_MIN / -1 divides without a remainder but still overflows to a float + if (!isA(withoutFloat.raw(), pt_ce_never_type)) { + return withoutFloat; + } + } + } + } + } + + return result; + } + + /* Mirrors getDivTypeFromTypes(). */ + static zv::Val getDivTypeFromTypes(zval *leftType, zval *rightType) + { + zv::Val result; + int constant = constantScalarArithmetic(leftType, rightType, IETR_DIV, result); + if (UNEXPECTED(constant < 0)) return zv::Val(); + if (constant == 0) return result; + + zv::Val optimizedLeft; + zv::Val optimizedRight; + if (constant == 1) { + optimizedLeft = optimizeScalarType(leftType); + if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); + leftType = optimizedLeft.raw(); + optimizedRight = optimizeScalarType(rightType); + if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); + rightType = optimizedRight.raw(); + } + + { + IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); + IETR_VAL(rightScalarValues, callOn(rightNumberType.raw(), mGetConstantScalarValues)); + if (UNEXPECTED(!requireArray(rightScalarValues.raw()))) return zv::Val(); + for (zv::ArrayEntry entry : zv::ArrRef(rightScalarValues.raw())) { + if (isZeroNumber(entry.value().raw())) { + return newErrorType(); + } + } + } + + return resolveCommonMath(IETR_DIV, leftType, rightType); + } + + /* Mirrors modulo(). */ + static zv::Val modulo(zval *leftType, zval *rightType) + { + if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { + return getNeverType(leftType, rightType); + } + + { + IETR_VAL(leftToNumber, callOn(leftType, mToNumber)); + if (isA(leftToNumber.raw(), pt_ce_error_type)) return newErrorType(); + IETR_VAL(rightToNumber, callOn(rightType, mToNumber)); + if (isA(rightToNumber.raw(), pt_ce_error_type)) return newErrorType(); + } + + zv::Val result; + int constant = constantScalarArithmetic(leftType, rightType, IETR_MOD, result); + if (UNEXPECTED(constant < 0)) return zv::Val(); + if (constant == 0) return result; + + zv::Val optimizedLeft; + zv::Val optimizedRight; + if (constant == 1) { + optimizedLeft = optimizeScalarType(leftType); + if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); + leftType = optimizedLeft.raw(); + optimizedRight = optimizeScalarType(rightType); + if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); + rightType = optimizedRight.raw(); + } + + { + IETR_VAL(integerType, callOn(rightType, mToInteger)); + if (isA(integerType.raw(), pt_ce_constant_integer_type)) { + zend_long value; + if (UNEXPECTED(!pt_constant_integer_get_value(Z_OBJ_P(integerType.raw()), value))) return zv::Val(); + if (value == 1) return newConstantIntegerType(0); + } + } + + { + IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); + IETR_VAL(rightScalarValues, callOn(rightNumberType.raw(), mGetConstantScalarValues)); + if (UNEXPECTED(!requireArray(rightScalarValues.raw()))) return zv::Val(); + for (zv::ArrayEntry entry : zv::ArrRef(rightScalarValues.raw())) { + if (isZeroNumber(entry.value().raw())) { + return newErrorType(); + } + } + } + + NullableLong maxMagnitude = NullableLong::null(); + { + IETR_VAL(rightInteger, callOn(rightType, mToInteger)); + bool hasDivisorBounds; + NullableLong divisorMin, divisorMax; + if (UNEXPECTED(!getIntegerBounds(rightInteger.raw(), hasDivisorBounds, divisorMin, divisorMax))) return zv::Val(); + if (hasDivisorBounds) { + maxMagnitude = getMaxModuloMagnitude(divisorMin, divisorMax); + } + } + + bool hasLeftBounds; + NullableLong leftMin, leftMax; + { + IETR_VAL(leftInteger, callOn(leftType, mToInteger)); + if (UNEXPECTED(!getIntegerBounds(leftInteger.raw(), hasLeftBounds, leftMin, leftMax))) return zv::Val(); + } + if (!hasLeftBounds) { + leftMin = NullableLong::null(); + leftMax = NullableLong::null(); + } + if (leftMin.isNull) { + IETR_VAL(nonNegative, fromInterval(NullableLong::of(0), NullableLong::null())); + IETR_TRI(leftIsNonNegative, triOn(nonNegative.raw(), mIsSuperTypeOf, 1, leftType)); + if (leftIsNonNegative == PT_TRI_YES) leftMin = NullableLong::of(0); + } + if (leftMax.isNull) { + IETR_VAL(nonPositive, fromInterval(NullableLong::null(), NullableLong::of(0))); + IETR_TRI(leftIsNonPositive, triOn(nonPositive.raw(), mIsSuperTypeOf, 1, leftType)); + if (leftIsNonPositive == PT_TRI_YES) leftMax = NullableLong::of(0); + } + + // The result has the sign of the dividend and is never bigger in magnitude than either + // the dividend or the largest magnitude the divisor can have. + NullableLong rangeMax; + if (!leftMax.isNull && leftMax.value <= 0) { + rangeMax = NullableLong::of(0); + } else if (maxMagnitude.isNull) { + rangeMax = leftMax; + } else if (leftMax.isNull) { + rangeMax = maxMagnitude; + } else { + rangeMax = NullableLong::of(std::min(leftMax.value, maxMagnitude.value)); + } + + NullableLong rangeMin; + if (!leftMin.isNull && leftMin.value >= 0) { + rangeMin = NullableLong::of(0); + } else if (maxMagnitude.isNull) { + rangeMin = leftMin; + } else if (leftMin.isNull) { + rangeMin = NullableLong::of(-maxMagnitude.value); + } else { + rangeMin = NullableLong::of(std::max(leftMin.value, -maxMagnitude.value)); + } + + 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 + * as unbounded too: abs(PHP_INT_MIN) does not fit into an integer, and the + * bound it stands for is PHP_INT_MAX, which is all an unbounded result can + * hold anyway */ + static NullableLong getMaxModuloMagnitude(NullableLong divisorMin, NullableLong divisorMax) + { + if (divisorMin.isNull || divisorMax.isNull || divisorMin.value == ZEND_LONG_MIN) return NullableLong::null(); + zend_long absMin = divisorMin.value < 0 ? -divisorMin.value : divisorMin.value; + zend_long absMax = divisorMax.value < 0 ? -divisorMax.value : divisorMax.value; + zend_long magnitude = std::max(absMin, absMax); + if (magnitude == 0) return NullableLong::null(); + return NullableLong::of(magnitude - 1); + } + + /* 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 */ + [[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; + } + + 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) { + unboundedMin = true; + } else if (min.isNull || innerMin.value < min.value) { + min = innerMin; + } + + if (innerMax.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; + } + } + + has = true; + outMin = unboundedMin ? NullableLong::null() : min; + outMax = unboundedMax ? NullableLong::null() : max; + return true; + } + + /* getIntegerBounds() as the private method returns it: the [min, max] + * pair or null; UNDEF = pending exception */ + static zv::Val getIntegerBoundsValue(zval *type) + { + bool has = false; + NullableLong min = NullableLong::null(); + NullableLong max = NullableLong::null(); + if (UNEXPECTED(!getIntegerBounds(type, has, min, max))) return zv::Val(); + if (!has) return zv::Val::null(); + zv::Arr pair = zv::Arr::create(2); + pair.push(min.toVal()); + pair.push(max.toVal()); + 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) + { + if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { + return getNeverType(leftType, rightType); + } + + zv::Val result; + int constant = constantScalarArithmetic(leftType, rightType, IETR_PLUS, result); + if (UNEXPECTED(constant < 0)) return zv::Val(); + if (constant == 0) return result; + + zv::Val optimizedLeft; + zv::Val optimizedRight; + if (constant == 1) { + optimizedLeft = optimizeScalarType(leftType); + if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); + leftType = optimizedLeft.raw(); + optimizedRight = optimizeScalarType(rightType); + if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); + rightType = optimizedRight.raw(); + } + + { + IETR_VAL(arrayUnion, tryArrayUnion(leftType, rightType)); + if (Z_TYPE_P(arrayUnion.raw()) != IS_NULL) return arrayUnion; + } + + IETR_TRI(leftIsArray, triOn(leftType, mIsArray)); + IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); + + bool leftMixed = isA(leftType, pt_ce_mixed_type); + bool rightMixed = isA(rightType, pt_ce_mixed_type); + if (leftMixed && rightMixed) { + IETR_VAL(floatType, newFloatType()); + IETR_VAL(integerType, newIntegerType()); + zv::Arr types = zv::Arr::create(3); + types.push(std::move(floatType)); + types.push(std::move(integerType)); + if (leftIsArray == PT_TRI_NO && rightIsArray == PT_TRI_NO) { + return newBenevolentUnionType(types); + } + IETR_VAL(keyMixed, newMixedType()); + IETR_VAL(valueMixed, newMixedType()); + IETR_VAL(arrayType, newArrayType(keyMixed.raw(), valueMixed.raw())); + types.push(std::move(arrayType)); + return newBenevolentUnionType(types); + } + + if ( + (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_NO) + || (leftIsArray == PT_TRI_NO && rightIsArray == PT_TRI_YES) + ) { + return newErrorType(); + } + + if ( + (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_MAYBE) + || (leftIsArray == PT_TRI_MAYBE && rightIsArray == PT_TRI_YES) + ) { + IETR_VAL(keyMixed, newMixedType()); + IETR_VAL(valueMixed, newMixedType()); + IETR_VAL(resultType, newArrayType(keyMixed.raw(), valueMixed.raw())); + IETR_TRI(leftAtLeastOnce, triOn(leftType, mIsIterableAtLeastOnce)); + bool nonEmpty = leftAtLeastOnce == PT_TRI_YES; + if (!nonEmpty) { + IETR_TRI(rightAtLeastOnce, triOn(rightType, mIsIterableAtLeastOnce)); + nonEmpty = rightAtLeastOnce == PT_TRI_YES; + } + if (nonEmpty) { + zval accessory; + if (UNEXPECTED(!pt_non_empty_array_type_new(&accessory))) return zv::Val(); + zv::Val accessoryHold = zv::Val::adopt(accessory); + zv::Args argv{resultType.raw(), accessoryHold.raw()}; + return pt_type_combinator_intersect(2, argv); + } + + return resultType; + } + + if (leftIsArray == PT_TRI_MAYBE && rightIsArray == PT_TRI_MAYBE) { + zv::Arr plusableTypes = zv::Arr::create(5); + { + IETR_VAL(stringType, newStringType()); + plusableTypes.push(std::move(stringType)); + IETR_VAL(floatType, newFloatType()); + plusableTypes.push(std::move(floatType)); + IETR_VAL(integerType, newIntegerType()); + plusableTypes.push(std::move(integerType)); + IETR_VAL(keyMixed, newMixedType()); + IETR_VAL(valueMixed, newMixedType()); + IETR_VAL(arrayType, newArrayType(keyMixed.raw(), valueMixed.raw())); + plusableTypes.push(std::move(arrayType)); + IETR_VAL(booleanType, newBooleanType()); + plusableTypes.push(std::move(booleanType)); + } + IETR_VAL(plusable, newUnionType(plusableTypes)); + + IETR_TRI(plusableSuperTypeOfLeftTri, triOn(plusable.raw(), mIsSuperTypeOf, 1, leftType)); + IETR_TRI(plusableSuperTypeOfRightTri, triOn(plusable.raw(), mIsSuperTypeOf, 1, rightType)); + bool plusableSuperTypeOfLeft = plusableSuperTypeOfLeftTri == PT_TRI_YES; + bool plusableSuperTypeOfRight = plusableSuperTypeOfRightTri == PT_TRI_YES; + if (plusableSuperTypeOfLeft && plusableSuperTypeOfRight) { + return union2(leftType, rightType); + } + if (plusableSuperTypeOfLeft && rightMixed) { + return zv::Val::copyOf(zv::Ref(leftType)); + } + if (plusableSuperTypeOfRight && leftMixed) { + return zv::Val::copyOf(zv::Ref(rightType)); + } + } + + return resolveCommonMath(IETR_PLUS, leftType, rightType); + } + + /* Mirrors arrayUnion(). */ + static zv::Val arrayUnion(zval *leftType, zval *rightType) + { + IETR_VAL(result, tryArrayUnion(leftType, rightType)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + return newErrorType(); + } + + /* Mirrors tryArrayUnion(): a Type, or PHP null */ + static zv::Val tryArrayUnion(zval *leftType, zval *rightType) + { + IETR_VAL(leftConstantArrays, callOn(leftType, mGetConstantArrays)); + IETR_VAL(rightConstantArrays, callOn(rightType, mGetConstantArrays)); + if (UNEXPECTED(!requireArray(leftConstantArrays.raw()) || !requireArray(rightConstantArrays.raw()))) return zv::Val(); + + zend_long leftCount = zend_hash_num_elements(Z_ARRVAL_P(leftConstantArrays.raw())); + zend_long rightCount = zend_hash_num_elements(Z_ARRVAL_P(rightConstantArrays.raw())); + if (leftCount > 0 && rightCount > 0 + && (leftCount + rightCount < PT_CONSTANT_ARRAY_TYPE_BUILDER_ARRAY_COUNT_LIMIT)) { + zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftCount * rightCount)); + for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantArrays.raw())) { + zval *rightConstantArray = rightEntry.value().raw(); + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { + zval *leftConstantArray = leftEntry.value().raw(); + IETR_VAL(newArrayBuilder, pt_constant_array_type_builder_create_from_constant_array(rightConstantArray)); + IETR_VAL(leftKeyTypes, callOn(leftConstantArray, mGetKeyTypes)); + if (UNEXPECTED(!requireArray(leftKeyTypes.raw()))) return zv::Val(); + for (zv::ArrayEntry keyEntry : zv::ArrRef(leftKeyTypes.raw())) { + zval *leftKeyType = keyEntry.value().raw(); + zval i; + entryKey(keyEntry, i); + IETR_VAL(optionalHold, callOn(leftConstantArray, mIsOptionalKey, 1, &i)); + bool optional = zend_is_true(optionalHold.raw()); + IETR_VAL(valueType, callOn(leftConstantArray, mGetOffsetValueType, 1, leftKeyType)); + zv::Val valueTypeHold = std::move(valueType); + if (!optional) { + IETR_TRI(rightHas, triOn(rightConstantArray, mHasOffsetValueType, 1, leftKeyType)); + if (rightHas == PT_TRI_MAYBE) { + IETR_VAL(rightValueType, callOn(rightConstantArray, mGetOffsetValueType, 1, leftKeyType)); + IETR_VAL(merged, union2(valueTypeHold.raw(), rightValueType.raw())); + valueTypeHold = std::move(merged); + } + } + if (UNEXPECTED(!pt_constant_array_type_builder_set_offset_value_type(newArrayBuilder.raw(), leftKeyType, valueTypeHold.raw(), optional))) return zv::Val(); + } + IETR_VAL(array, pt_constant_array_type_builder_get_array(newArrayBuilder.raw())); + resultTypes.push(std::move(array)); + } + } + return unionOf(resultTypes); + } + + // `array{...} + array` keeps the known keyed prefix and + // folds the right-hand side into an open `...` tail. This + // only applies when the left arrays' sealedness is known (bleeding edge); + // otherwise the legacy behavior below is kept for backward compatibility. + if (leftCount > 0 && rightCount == 0 + && leftCount < PT_CONSTANT_ARRAY_TYPE_BUILDER_ARRAY_COUNT_LIMIT) { + IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); + if (rightIsArray == PT_TRI_YES) { + bool allSealednessKnown = true; + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { + IETR_TRI(unsealed, triOn(leftEntry.value().raw(), mIsUnsealed)); + if (unsealed == PT_TRI_MAYBE) { + allSealednessKnown = false; + break; + } + } + + if (allSealednessKnown) { + IETR_VAL(rightKeyType, callOn(rightType, mGetIterableKeyType)); + IETR_VAL(rightValueType, callOn(rightType, mGetIterableValueType)); + zv::Arr resultTypes = zv::Arr::create((uint32_t) leftCount); + for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { + zval *leftConstantArray = leftEntry.value().raw(); + IETR_VAL(newArrayBuilder, pt_constant_array_type_builder_create_from_constant_array(leftConstantArray)); + IETR_VAL(existingUnsealed, callOn(leftConstantArray, mGetUnsealedTypes)); + IETR_TRI(unsealed, triOn(leftConstantArray, mIsUnsealed)); + if (unsealed == PT_TRI_YES && Z_TYPE_P(existingUnsealed.raw()) != IS_NULL) { + zval *existingKey = arrayIndex(existingUnsealed.raw(), 0); + if (UNEXPECTED(existingKey == NULL)) return zv::Val(); + IETR_VAL(keyUnion, union2(existingKey, rightKeyType.raw())); + zval *existingValue = arrayIndex(existingUnsealed.raw(), 1); + if (UNEXPECTED(existingValue == NULL)) return zv::Val(); + IETR_VAL(valueUnion, union2(existingValue, rightValueType.raw())); + if (UNEXPECTED(!pt_constant_array_type_builder_make_unsealed(newArrayBuilder.raw(), keyUnion.raw(), valueUnion.raw()))) return zv::Val(); + } else { + if (UNEXPECTED(!pt_constant_array_type_builder_make_unsealed(newArrayBuilder.raw(), rightKeyType.raw(), rightValueType.raw()))) return zv::Val(); + } + IETR_VAL(array, pt_constant_array_type_builder_get_array(newArrayBuilder.raw())); + resultTypes.push(std::move(array)); + } + return unionOf(resultTypes); + } + } + } + + IETR_TRI(leftIsArray, triOn(leftType, mIsArray)); + IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); + if (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_YES) { + IETR_VAL(leftKeyTypeForEquals, callOn(leftType, mGetIterableKeyType)); + IETR_VAL(rightKeyTypeForEquals, callOn(rightType, mGetIterableKeyType)); + zv::Val keyType; + { + zv::Val equalsHold = callOn(leftKeyTypeForEquals.raw(), mEquals, 1, rightKeyTypeForEquals.raw()); + if (UNEXPECTED(equalsHold.isUndef())) return zv::Val(); + if (zend_is_true(equalsHold.raw())) { + // to preserve BenevolentUnionType + keyType = callOn(leftType, mGetIterableKeyType); + } else { + IETR_VAL(leftKey, callOn(leftType, mGetIterableKeyType)); + IETR_VAL(rightKey, callOn(rightType, mGetIterableKeyType)); + keyType = union2(leftKey.raw(), rightKey.raw()); + } + if (UNEXPECTED(keyType.isUndef())) return zv::Val(); + } + + IETR_VAL(leftIterableValueType, callOn(leftType, mGetIterableValueType)); + IETR_VAL(rightIterableValueType, callOn(rightType, mGetIterableValueType)); + IETR_VAL(valueUnion, union2(leftIterableValueType.raw(), rightIterableValueType.raw())); + IETR_VAL(arrayType, newArrayType(keyType.raw(), valueUnion.raw())); + + zv::Arr accessories = zv::Arr::create(4); + if (leftCount > 0) { + // Use the first constant array as a reference to list potential offsets. + // We only need to check the first array because we're looking for offsets that exist in ALL arrays. + zval *constantArray = arrayIndex(leftConstantArrays.raw(), 0); + if (UNEXPECTED(constantArray == NULL)) return zv::Val(); + IETR_VAL(keyTypes, callOn(constantArray, mGetKeyTypes)); + if (UNEXPECTED(!requireArray(keyTypes.raw()))) return zv::Val(); + for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { + zval *offsetType = keyEntry.value().raw(); + IETR_TRI(hasOffset, triOn(leftType, mHasOffsetValueType, 1, offsetType)); + if (hasOffset != PT_TRI_YES) { + continue; + } + + IETR_VAL(valueType, callOn(leftType, mGetOffsetValueType, 1, offsetType)); + zval accessory; + if (UNEXPECTED(!pt_has_offset_value_type_new(&accessory, offsetType, valueType.raw()))) return zv::Val(); + accessories.push(zv::Val::adopt(accessory)); + } + } + + if (rightCount > 0) { + // Use the first constant array as a reference to list potential offsets. + // We only need to check the first array because we're looking for offsets that exist in ALL arrays. + zval *constantArray = arrayIndex(rightConstantArrays.raw(), 0); + if (UNEXPECTED(constantArray == NULL)) return zv::Val(); + IETR_VAL(keyTypes, callOn(constantArray, mGetKeyTypes)); + if (UNEXPECTED(!requireArray(keyTypes.raw()))) return zv::Val(); + for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { + zval *offsetType = keyEntry.value().raw(); + IETR_TRI(hasOffset, triOn(rightType, mHasOffsetValueType, 1, offsetType)); + if (hasOffset != PT_TRI_YES) { + continue; + } + + IETR_VAL(rightOffsetValueType, callOn(rightType, mGetOffsetValueType, 1, offsetType)); + IETR_VAL(valueType, union2(leftIterableValueType.raw(), rightOffsetValueType.raw())); + zval accessory; + if (UNEXPECTED(!pt_has_offset_value_type_new(&accessory, offsetType, valueType.raw()))) return zv::Val(); + accessories.push(zv::Val::adopt(accessory)); + } + } + + IETR_TRI(leftAtLeastOnce, triOn(leftType, mIsIterableAtLeastOnce)); + bool nonEmpty = leftAtLeastOnce == PT_TRI_YES; + if (!nonEmpty) { + IETR_TRI(rightAtLeastOnce, triOn(rightType, mIsIterableAtLeastOnce)); + nonEmpty = rightAtLeastOnce == PT_TRI_YES; + } + if (nonEmpty) { + zval accessory; + if (UNEXPECTED(!pt_non_empty_array_type_new(&accessory))) return zv::Val(); + accessories.push(zv::Val::adopt(accessory)); + } + IETR_TRI(leftIsList, triOn(leftType, mIsList)); + if (leftIsList == PT_TRI_YES) { + IETR_TRI(rightIsList, triOn(rightType, mIsList)); + if (rightIsList == PT_TRI_YES) { + zval accessory; + if (UNEXPECTED(!pt_accessory_array_list_type_new(&accessory))) return zv::Val(); + accessories.push(zv::Val::adopt(accessory)); + } + } + + uint32_t accessoryCount = zend_hash_num_elements(accessories.table()); + if (accessoryCount > 0) { + zv::Arr intersectArgs = zv::Arr::create(accessoryCount + 1); + intersectArgs.push(std::move(arrayType)); + for (zv::ArrayEntry entry : zv::ArrRef(accessories.raw())) { + intersectArgs.push(entry.value()); + } + return pt_type_combinator_intersect(accessoryCount + 1, intersectArgs.table()->arPacked); + } + + return arrayType; + } + + return zv::Val::null(); + } + + /* Mirrors subtract() / multiply() (kind IETR_MINUS / IETR_MUL). */ + static zv::Val subtractOrMultiply(IetrBinaryKind kind, zval *leftType, zval *rightType) + { + zv::Val result; + int constant = constantScalarArithmetic(leftType, rightType, kind, result); + if (UNEXPECTED(constant < 0)) return zv::Val(); + if (constant == 0) return result; + + zv::Val optimizedLeft; + zv::Val optimizedRight; + if (constant == 1) { + optimizedLeft = optimizeScalarType(leftType); + if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); + leftType = optimizedLeft.raw(); + optimizedRight = optimizeScalarType(rightType); + if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); + rightType = optimizedRight.raw(); + } + + if (kind == IETR_MUL) { + { + IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); + int zero = isConstantIntegerZero(leftNumberType.raw()); + if (UNEXPECTED(zero < 0)) return zv::Val(); + if (zero) { + IETR_TRI(rightIsFloat, triOn(rightType, mIsFloat)); + if (rightIsFloat == PT_TRI_YES) { + return newConstantFloatType(0.0); + } + return newConstantIntegerType(0); + } + } + { + IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); + int zero = isConstantIntegerZero(rightNumberType.raw()); + if (UNEXPECTED(zero < 0)) return zv::Val(); + if (zero) { + IETR_TRI(leftIsFloat, triOn(leftType, mIsFloat)); + if (leftIsFloat == PT_TRI_YES) { + return newConstantFloatType(0.0); + } + return newConstantIntegerType(0); + } + } + } + + return resolveCommonMath(kind, leftType, rightType); + } + + /* Mirrors shiftLeft() / shiftRight(). */ + static zv::Val shift(IetrBinaryKind kind, zval *leftType, zval *rightType) + { + if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { + return getNeverType(leftType, rightType); + } + + zv::Val result; + int constant = constantScalarArithmetic(leftType, rightType, kind, result); + if (UNEXPECTED(constant < 0)) return zv::Val(); + if (constant == 0) return result; + + zv::Val optimizedLeft; + zv::Val optimizedRight; + if (constant == 1) { + optimizedLeft = optimizeScalarType(leftType); + if (UNEXPECTED(optimizedLeft.isUndef())) return zv::Val(); + leftType = optimizedLeft.raw(); + optimizedRight = optimizeScalarType(rightType); + if (UNEXPECTED(optimizedRight.isUndef())) return zv::Val(); + rightType = optimizedRight.raw(); + } + + { + IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); + IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); + + if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { + return newErrorType(); + } + } + + return resolveCommonMath(kind, leftType, rightType); + } + + /* Mirrors optimizeScalarType(). */ + static zv::Val optimizeScalarType(zval *type) + { + zv::Arr types = zv::Arr::create(4); + { + IETR_TRI(isInteger, triOn(type, mIsInteger)); + if (isInteger == PT_TRI_YES) { + IETR_VAL(integerType, newIntegerType()); + types.push(std::move(integerType)); + } + } + { + IETR_TRI(isString, triOn(type, mIsString)); + if (isString == PT_TRI_YES) { + IETR_VAL(stringType, newStringType()); + types.push(std::move(stringType)); + } + } + { + IETR_TRI(isFloat, triOn(type, mIsFloat)); + if (isFloat == PT_TRI_YES) { + IETR_VAL(floatType, newFloatType()); + types.push(std::move(floatType)); + } + } + { + IETR_TRI(isNull, triOn(type, mIsNull)); + if (isNull == PT_TRI_YES) { + IETR_VAL(nullType, newNullType()); + types.push(std::move(nullType)); + } + } + + uint32_t count = zend_hash_num_elements(types.table()); + if (count == 0) { + return newErrorType(); + } + + if (count == 1) { + return zv::Val::copyOf(zv::Ref(zend_hash_index_find(types.table(), 0))); + } + + return newUnionType(types); + } + + /* Mirrors getNonNegativeIntegerBounds(): true fills min/max (max null + * 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; + } + + /* Mirrors getNonNegativeIntegerBounds() as the PHP value it returns: + * [min, max] or null */ + static zv::Val getNonNegativeIntegerBoundsValue(zval *type) + { + zend_long min = 0; + NullableLong max = NullableLong::null(); + int bounds = getNonNegativeIntegerBounds(type, min, max); + if (UNEXPECTED(bounds < 0)) return zv::Val(); + if (bounds == 0) return zv::Val::null(); + zv::Arr pair = zv::Arr::create(2); + pair.push(zv::Val::integer(min)); + pair.push(max.toVal()); + return zv::Val(std::move(pair)); + } + + /* Mirrors computeBitwiseAndRange(): a Type, or PHP null */ + static zv::Val computeBitwiseAndRange(zval *leftNumberType, zval *rightNumberType) + { + zend_long leftMin = 0, rightMin = 0; + NullableLong leftMax = NullableLong::null(), rightMax = NullableLong::null(); + int leftBounds = getNonNegativeIntegerBounds(leftNumberType, leftMin, leftMax); + if (UNEXPECTED(leftBounds < 0)) return zv::Val(); + int rightBounds = getNonNegativeIntegerBounds(rightNumberType, rightMin, rightMax); + if (UNEXPECTED(rightBounds < 0)) return zv::Val(); + if (leftBounds == 0 && rightBounds == 0) { + return zv::Val::null(); + } + + bool anyMax = false; + zend_long minOfMax = 0; + if (leftBounds == 1 && !leftMax.isNull) { + minOfMax = leftMax.value; + anyMax = true; + } + if (rightBounds == 1 && !rightMax.isNull) { + minOfMax = anyMax && minOfMax <= rightMax.value ? minOfMax : rightMax.value; + anyMax = true; + } + + return fromInterval(NullableLong::of(0), anyMax ? NullableLong::of(minOfMax) : NullableLong::null()); + } + + /* Mirrors computeBitwiseOrXorRange(): a Type, or PHP null */ + static zv::Val computeBitwiseOrXorRange(zval *leftNumberType, zval *rightNumberType) + { + zend_long leftMin = 0, rightMin = 0; + NullableLong leftMax = NullableLong::null(), rightMax = NullableLong::null(); + int leftBounds = getNonNegativeIntegerBounds(leftNumberType, leftMin, leftMax); + if (UNEXPECTED(leftBounds < 0)) return zv::Val(); + int rightBounds = getNonNegativeIntegerBounds(rightNumberType, rightMin, rightMax); + if (UNEXPECTED(rightBounds < 0)) return zv::Val(); + if (leftBounds == 0 || rightBounds == 0) { + return zv::Val::null(); + } + if (leftMax.isNull || rightMax.isNull) { + return fromInterval(NullableLong::of(0), NullableLong::null()); + } + + return fromInterval(NullableLong::of(0), NullableLong::of(allBitsMask(leftMax.value >= rightMax.value ? leftMax.value : rightMax.value))); + } + + /* Mirrors allBitsMask(). */ + static zend_long allBitsMask(zend_long value) + { + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + + return value; + } + + /* }}} */ + + /* {{{ resolveCommonMath() / integerRangeMath() */ + + /* Mirrors resolveCommonMath() ($operator as `kind`). */ + static zv::Val resolveCommonMath(IetrBinaryKind kind, zval *leftType, zval *rightType) + { + 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); + } + + return callOn(union_.raw(), mToNumber); + } + } + + { + IETR_TRI(leftIsArray, triOn(leftType, mIsArray)); + if (leftIsArray == PT_TRI_YES) return newErrorType(); + IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); + if (rightIsArray == PT_TRI_YES) return newErrorType(); + IETR_TRI(typesIsArray, triOn(types.raw(), mIsArray)); + if (typesIsArray == PT_TRI_YES) return newErrorType(); + } + + if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { + return newErrorType(); + } + if (isA(leftNumberType.raw(), pt_ce_never_type) || isA(rightNumberType.raw(), pt_ce_never_type)) { + return getNeverType(leftNumberType.raw(), rightNumberType.raw()); + } + + { + IETR_TRI(leftIsFloat, triOn(leftNumberType.raw(), mIsFloat)); + bool isFloat = leftIsFloat == PT_TRI_YES; + if (!isFloat) { + IETR_TRI(rightIsFloat, triOn(rightNumberType.raw(), mIsFloat)); + isFloat = rightIsFloat == PT_TRI_YES; + } + if (isFloat) { + if (kind == IETR_SHIFT_LEFT || kind == IETR_SHIFT_RIGHT) { + return newIntegerType(); + } + return newFloatType(); + } + } + + IETR_VAL(resultType, union2(leftNumberType.raw(), rightNumberType.raw())); + if (kind == IETR_DIV) { + bool benevolent = typesMixed; + if (!benevolent) { + IETR_TRI(resultIsInteger, triOn(resultType.raw(), mIsInteger)); + benevolent = resultIsInteger == PT_TRI_YES; + } + return newIntFloatUnion(benevolent); + } + + if (typesMixed + || isA(leftType, pt_ce_benevolent_union_type) + || isA(rightType, pt_ce_benevolent_union_type) + ) { + return toBenevolentUnion(resultType.raw()); + } + + 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: + if (operandIsConstant) { + if (Z_TYPE(rangeMin) != IS_NULL && UNEXPECTED(!scalarOp(sub_function, &rangeMin, operandValue, &min))) return zv::Val(); + if (Z_TYPE(rangeMax) != IS_NULL && UNEXPECTED(!scalarOp(sub_function, &rangeMax, operandValue, &max))) return zv::Val(); + } else { + if (identical(&rangeMin, &rangeMax) && Z_TYPE(rangeMin) != IS_NULL + && (Z_TYPE(operandMin) == IS_NULL || Z_TYPE(operandMax) == IS_NULL)) { + ZVAL_NULL(&min); + ZVAL_COPY_VALUE(&max, &rangeMin); + } else { + if (Z_TYPE(operandMin) == IS_NULL) { + ZVAL_NULL(&min); + } else if (Z_TYPE(rangeMin) != IS_NULL) { + if (Z_TYPE(operandMax) != IS_NULL) { + if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); + } else { + if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMin, &min))) return zv::Val(); + } + } else { + ZVAL_NULL(&min); + } + + if (Z_TYPE(operandMax) == IS_NULL) { + ZVAL_NULL(&min); + ZVAL_NULL(&max); + } else if (Z_TYPE(rangeMax) != IS_NULL) { + if (Z_TYPE(rangeMin) != IS_NULL && Z_TYPE(operandMin) == IS_NULL) { + if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); + ZVAL_NULL(&max); + } else if (Z_TYPE(operandMin) != IS_NULL) { + if (UNEXPECTED(!scalarOp(sub_function, &rangeMax, &operandMin, &max))) return zv::Val(); + } else { + ZVAL_NULL(&max); + } + } else { + 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); + } + } + } + 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() */ + + /* Mirrors negateNumber(). */ + static zv::Val negateNumber(zval *typeIn) + { + IETR_VAL(type, callOn(typeIn, mToNumber)); + IETR_VAL(scalarValues, callOn(type.raw(), mGetConstantScalarValues)); + if (UNEXPECTED(!requireCountable(scalarValues.raw()))) return zv::Val(); + + uint32_t count = zend_hash_num_elements(Z_ARRVAL_P(scalarValues.raw())); + if (count > 0) { + zv::Arr newTypes = zv::Arr::create(count); + for (zv::ArrayEntry entry : zv::ArrRef(scalarValues.raw())) { + zval *scalarValue = entry.value().raw(); + ZVAL_DEREF(scalarValue); + if (Z_TYPE_P(scalarValue) == IS_LONG) { + zval minusOne, newValue; + ZVAL_LONG(&minusOne, -1); + if (UNEXPECTED(!scalarOp(mul_function, scalarValue, &minusOne, &newValue))) return zv::Val(); + if (Z_TYPE(newValue) != IS_LONG) { + // Negating the smallest integer overflows into a float. + IETR_VAL(floatType, newConstantFloatType(zval_get_double(&newValue))); + newTypes.push(std::move(floatType)); + continue; + } + IETR_VAL(integerType, newConstantIntegerType(Z_LVAL(newValue))); + newTypes.push(std::move(integerType)); + } else if (Z_TYPE_P(scalarValue) == IS_DOUBLE) { + zval minusOne, newValue; + ZVAL_LONG(&minusOne, -1); + if (UNEXPECTED(!scalarOp(mul_function, scalarValue, &minusOne, &newValue))) return zv::Val(); + IETR_VAL(floatType, newConstantFloatType(Z_DVAL(newValue))); + newTypes.push(std::move(floatType)); + } + } + + return unionOf(newTypes); + } + + return type; + } + + /* Mirrors negate(). */ + static zv::Val negate(zval *type) + { + IETR_VAL(negated, negateNumber(type)); + if (isA(negated.raw(), pt_ce_integer_range_type)) { + IETR_VAL(minusOne, newConstantIntegerType(-1)); + return subtractOrMultiply(IETR_MUL, type, minusOne.raw()); + } + + return negated; + } + + /* }}} */ + + /* {{{ small private helpers of the twin */ + + /* Mirrors getNeverType(). */ + static zv::Val getNeverType(zval *leftType, zval *rightType) + { + // make sure we don't lose the explicit flag in the process + if (isA(leftType, pt_ce_never_type)) { + bool isExplicit; + if (UNEXPECTED(!pt_never_type_is_explicit(Z_OBJ_P(leftType), isExplicit))) return zv::Val(); + if (isExplicit) return zv::Val::copyOf(zv::Ref(leftType)); + } + if (isA(rightType, pt_ce_never_type)) { + bool isExplicit; + if (UNEXPECTED(!pt_never_type_is_explicit(Z_OBJ_P(rightType), isExplicit))) return zv::Val(); + if (isExplicit) return zv::Val::copyOf(zv::Ref(rightType)); + } + return newNeverType(); + } + + /* Mirrors getTypeFromValue(). */ + static zv::Val getTypeFromValue(zval *value) + { + return typeFromValue(value); + } + + /* }}} */ + + /* 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 + * (inf -1 / 1) for an unbounded end */ + struct Bound + { + int inf; + zend_long value; + + static Bound of(zend_long value) { return { 0, value }; } + static Bound infinity(int sign) { return { sign, 0 }; } + + bool lessThan(const Bound &other) const + { + if (inf != other.inf) return inf < other.inf; + return inf == 0 && value < other.value; + } + + /* the int|float the twin's private methods trade */ + void toZval(zval *out) const + { + if (inf != 0) { + ZVAL_DOUBLE(out, inf * std::numeric_limits::infinity()); + } else { + ZVAL_LONG(out, value); + } + } + + /* an int|float parameter of the twin's private methods; false = + * TypeError thrown */ + [[nodiscard]] static bool fromZval(zval *value, uint32_t arg, Bound &out) + { + if (Z_TYPE_P(value) == IS_LONG) { + out = of(Z_LVAL_P(value)); + return true; + } + if (Z_TYPE_P(value) == IS_DOUBLE && std::isinf(Z_DVAL_P(value))) { + out = infinity(Z_DVAL_P(value) > 0 ? 1 : -1); + return true; + } + if (Z_TYPE_P(value) == IS_DOUBLE && Z_DVAL_P(value) == std::trunc(Z_DVAL_P(value)) && Z_DVAL_P(value) >= (double) ZEND_LONG_MIN && Z_DVAL_P(value) < (double) ZEND_LONG_MAX) { + out = of((zend_long) Z_DVAL_P(value)); + return true; + } + zend_argument_type_error(arg, "must be of type int|float, %s given", zend_zval_value_name(value)); + return false; + } + }; + + struct Interval + { + Bound min; + Bound max; + }; + + /* Mirrors intDivide(). */ + static zv::Val intDivide(zval *numeratorType, zval *divisorType) + { + std::vector numeratorIntervals; + std::vector divisorIntervals; + bool numeratorKnown, divisorKnown; + { + IETR_VAL(numeratorInteger, callOn(numeratorType, mToInteger)); + if (UNEXPECTED(!getIntervals(numeratorInteger.raw(), numeratorIntervals, numeratorKnown))) return zv::Val(); + IETR_VAL(divisorInteger, callOn(divisorType, mToInteger)); + if (UNEXPECTED(!getIntervals(divisorInteger.raw(), divisorIntervals, divisorKnown))) return zv::Val(); + } + if (!numeratorKnown || !divisorKnown) { + return newErrorType(); + } + + zv::Arr resultTypes = zv::Arr::create((uint32_t) (numeratorIntervals.size() * divisorIntervals.size() * 2)); + for (const Interval &numerator : numeratorIntervals) { + for (const Interval &divisor : divisorIntervals) { + if (divisor.min.lessThan(Bound::of(0))) { + Bound minusOne = Bound::of(-1); + IETR_VAL(negative, divideIntervals(numerator.min, numerator.max, divisor.min, minusOne.lessThan(divisor.max) ? minusOne : divisor.max)); + resultTypes.push(std::move(negative)); + } + if (!Bound::of(0).lessThan(divisor.max)) { + continue; + } + + Bound one = Bound::of(1); + IETR_VAL(positive, divideIntervals(numerator.min, numerator.max, divisor.min.lessThan(one) ? one : divisor.min, divisor.max)); + resultTypes.push(std::move(positive)); + } + } + + if (zend_hash_num_elements(resultTypes.table()) == 0) { + return newErrorType(); + } + + return unionOf(resultTypes); + } + + /* Mirrors getIntervals(): `known` false for the twin's null; false = + * pending exception */ + [[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); + if (UNEXPECTED(isInteger < 0)) return false; + if (isInteger == PT_TRI_YES) { + intervals.push_back({ Bound::infinity(-1), Bound::infinity(1) }); + return true; + } + ok = false; + 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; + } + } + + known = true; + return true; + } + + /* getIntervals() as the private method returns it: a list of [min, max] + * pairs or null; UNDEF = pending exception */ + static zv::Val getIntervalsValue(zval *type) + { + std::vector intervals; + bool known; + if (UNEXPECTED(!getIntervals(type, intervals, known))) return zv::Val(); + if (!known) return zv::Val::null(); + zv::Arr list = zv::Arr::create((uint32_t) intervals.size()); + for (const Interval &interval : intervals) { + zval min, max; + interval.min.toZval(&min); + interval.max.toZval(&max); + zv::Arr pair = zv::Arr::create(2); + pair.push(zv::Val::adopt(min)); + pair.push(zv::Val::adopt(max)); + list.push(zv::Val(std::move(pair))); + } + return zv::Val(std::move(list)); + } + + /* Mirrors divideIntervals(). */ + static zv::Val divideIntervals(Bound numeratorMin, Bound numeratorMax, Bound divisorMin, Bound divisorMax) + { + Bound results[4] = { + divideBounds(numeratorMin, divisorMin), + divideBounds(numeratorMin, divisorMax), + divideBounds(numeratorMax, divisorMin), + divideBounds(numeratorMax, divisorMax), + }; + Bound min = results[0]; + Bound max = results[0]; + for (const Bound &result : results) { + if (result.lessThan(min)) min = result; + if (max.lessThan(result)) max = result; + } + + return fromInterval( + min.inf != 0 ? NullableLong::null() : NullableLong::of(min.value), + max.inf != 0 ? NullableLong::null() : NullableLong::of(max.value) + ); + } + + /* Mirrors divideBounds(). */ + static Bound divideBounds(Bound numerator, Bound divisor) + { + if (numerator.inf != 0) { + if (divisor.inf != 0) { + // the range already contains 0 through a neighbouring corner + return Bound::of(0); + } + + return (numerator.inf > 0) == (divisor.value > 0) ? Bound::infinity(1) : Bound::infinity(-1); + } + + if (divisor.inf != 0) { + return Bound::of(0); + } + + if (numerator.value == ZEND_LONG_MIN && divisor.value == -1) { + // throws ArithmeticError, the closest valid result is intdiv(PHP_INT_MIN + 1, -1) + return Bound::of(ZEND_LONG_MAX); + } + + return Bound::of(numerator.value / divisor.value); + } + + /* }}} */ + +}; + +} // namespace phpstanturbo + +using phpstanturbo::OperatorTypeHelper; +using phpstanturbo::NullableLong; + +/* {{{ direct entries */ + +zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion) +{ + (void) phpVersion; + switch (op) { + case PT_OPERATOR_ADD: + return OperatorTypeHelper::add(left, right); + case PT_OPERATOR_SUBTRACT: + return OperatorTypeHelper::subtractOrMultiply(IETR_MINUS, left, right); + case PT_OPERATOR_MULTIPLY: + return OperatorTypeHelper::subtractOrMultiply(IETR_MUL, left, right); + case PT_OPERATOR_DIVIDE: + return OperatorTypeHelper::divide(left, right); + case PT_OPERATOR_INT_DIVIDE: + return OperatorTypeHelper::intDivide(left, right); + case PT_OPERATOR_MODULO: + return OperatorTypeHelper::modulo(left, right); + case PT_OPERATOR_BITWISE_AND: + return OperatorTypeHelper::bitwise(IETR_BITWISE_AND, left, right); + case PT_OPERATOR_BITWISE_OR: + return OperatorTypeHelper::bitwise(IETR_BITWISE_OR, left, right); + 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); + case PT_OPERATOR_SHIFT_RIGHT: + return OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, left, right); + case PT_OPERATOR_CONCATENATE: + return OperatorTypeHelper::concat(left, right); + case PT_OPERATOR_ARRAY_UNION: + return OperatorTypeHelper::arrayUnion(left, right); + } + pt_throw_should_not_happen(); + return zv::Val(); +} + +zv::Val pt_operator_type_helper_negate(zval *type, zval *phpVersion) +{ + (void) phpVersion; + return OperatorTypeHelper::negate(type); +} + +/* }}} */ + +/* {{{ engine ABI glue: parameter parsing + registration */ + +namespace { + +/* the engine's TypeError of a Type parameter the twin declares (the + * internal arginfo does not enforce it); false = thrown */ +bool otArgType(zval *value, uint32_t arg) +{ + zend_class_entry *ce = pt_class(PT_CLASS_TYPE); + if (UNEXPECTED(ce == NULL)) return false; + if (EXPECTED(Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce))) return true; + zend_argument_type_error(arg, "must be of type PHPStan\\Type\\Type, %s given", zend_zval_value_name(value)); + return false; +} + +bool otArgPhpVersion(zval *value, uint32_t arg) +{ + zend_class_entry *ce = pt_class(PT_CLASS_PHP_VERSION); + if (UNEXPECTED(ce == NULL)) return false; + if (EXPECTED(Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce))) return true; + zend_argument_type_error(arg, "must be of type PHPStan\\Php\\PhpVersion, %s given", zend_zval_value_name(value)); + return false; +} + +/* the operator string of resolveCommonMath() / integerRangeMath(); false = + * the ValueError of an operator the twin does not handle */ +bool otOperatorKind(zend_string *operator_, IetrBinaryKind &kind) +{ + static constexpr struct + { + const char *operator_; + IetrBinaryKind kind; + } operators[] = { + { "+", IETR_PLUS }, + { "-", IETR_MINUS }, + { "*", IETR_MUL }, + { "/", IETR_DIV }, + { "<<", IETR_SHIFT_LEFT }, + { ">>", IETR_SHIFT_RIGHT }, + }; + for (const auto &candidate : operators) { + if (zend_string_equals_cstr(operator_, candidate.operator_, strlen(candidate.operator_))) { + kind = candidate.kind; + return true; + } + } + kind = IETR_OTHER; + return true; +} + +/* the (Type $left, Type $right, PhpVersion $phpVersion) glue */ +#define OT_BINARY_GLUE(body) \ + [](INTERNAL_FUNCTION_PARAMETERS) { \ + zval *leftType, *rightType, *phpVersion; \ + if (!zp::parse(execute_data, leftType, rightType, phpVersion)) RETURN_THROWS(); \ + if (UNEXPECTED(!otArgType(leftType, 1) || !otArgType(rightType, 2) || !otArgPhpVersion(phpVersion, 3))) RETURN_THROWS(); \ + PT_RETURN_VAL(body); \ + } + +/* the (Type $leftType, Type $rightType) glue */ +#define OT_TYPES_GLUE(body) \ + [](INTERNAL_FUNCTION_PARAMETERS) { \ + zval *leftType, *rightType; \ + if (!zp::parse(execute_data, leftType, rightType)) RETURN_THROWS(); \ + if (UNEXPECTED(!otArgType(leftType, 1) || !otArgType(rightType, 2))) RETURN_THROWS(); \ + PT_RETURN_VAL(body); \ + } + +/* the (Type $type) glue */ +#define OT_TYPE_GLUE(body) \ + [](INTERNAL_FUNCTION_PARAMETERS) { \ + zval *type; \ + if (!zp::parse(execute_data, type)) RETURN_THROWS(); \ + if (UNEXPECTED(!otArgType(type, 1))) RETURN_THROWS(); \ + PT_RETURN_VAL(body); \ + } + +} // namespace + +PT_MINIT_REGISTRATION(pt_register_operator_type_helper) +{ + reg::Class cls("PHPStan\\Type\\OperatorTypeHelper"); + ptdecl::OperatorTypeHelper::declareClass(cls); + cls.privateClassConstantLong("IS_SCALAR_TYPE", PT_IETR_IS_SCALAR_TYPE); + cls.privateClassConstantLong("IS_UNKNOWN", PT_IETR_IS_UNKNOWN); + ptdecl::OperatorTypeHelper::declareProperties(cls); + + cls.method(sigs::concat, OT_BINARY_GLUE(OperatorTypeHelper::concat(leftType, rightType))); + cls.method(sigs::bitwiseAnd, OT_BINARY_GLUE(OperatorTypeHelper::bitwise(IETR_BITWISE_AND, leftType, rightType))); + cls.method(sigs::bitwiseOr, OT_BINARY_GLUE(OperatorTypeHelper::bitwise(IETR_BITWISE_OR, leftType, rightType))); + cls.method(sigs::bitwiseXor, OT_BINARY_GLUE(OperatorTypeHelper::bitwise(IETR_BITWISE_XOR, leftType, rightType))); + + cls.method(sigs::getFiniteOrConstantScalarTypes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *leftType, *rightType, *operationCallable; + if (!zp::parse(execute_data, leftType, rightType, operationCallable)) RETURN_THROWS(); + if (UNEXPECTED(!otArgType(leftType, 1) || !otArgType(rightType, 2))) RETURN_THROWS(); + if (UNEXPECTED(!zend_is_callable(operationCallable, 0, NULL))) { + zend_argument_type_error(3, "must be of type callable, %s given", zend_zval_value_name(operationCallable)); + RETURN_THROWS(); + } + zv::Val result; + zend_long code = OperatorTypeHelper::getFiniteOrConstantScalarTypesWith(leftType, rightType, NULL, operationCallable, result); + if (UNEXPECTED(code < 0)) RETURN_THROWS(); + if (code == 0) PT_RETURN_VAL(std::move(result)); + RETURN_LONG(code); + }); + + cls.method(sigs::divide, OT_BINARY_GLUE(OperatorTypeHelper::divide(leftType, rightType))); + cls.method(sigs::getDivTypeFromTypes, OT_TYPES_GLUE(OperatorTypeHelper::getDivTypeFromTypes(leftType, rightType))); + cls.method(sigs::modulo, OT_BINARY_GLUE(OperatorTypeHelper::modulo(leftType, rightType))); + + cls.method(sigs::getMaxModuloMagnitude, [](INTERNAL_FUNCTION_PARAMETERS) { + zend_long divisorMin, divisorMax; + bool divisorMinIsNull, divisorMaxIsNull; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG_OR_NULL(divisorMin, divisorMinIsNull) + Z_PARAM_LONG_OR_NULL(divisorMax, divisorMaxIsNull) + ZEND_PARSE_PARAMETERS_END(); + NullableLong magnitude = OperatorTypeHelper::getMaxModuloMagnitude( + divisorMinIsNull ? NullableLong::null() : NullableLong::of(divisorMin), + divisorMaxIsNull ? NullableLong::null() : NullableLong::of(divisorMax)); + PT_RETURN_VAL(magnitude.toVal()); + }); + + cls.method(sigs::getIntegerBounds, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerBoundsValue(type))); + cls.method(sigs::add, OT_BINARY_GLUE(OperatorTypeHelper::add(leftType, rightType))); + cls.method(sigs::arrayUnion, OT_BINARY_GLUE(OperatorTypeHelper::arrayUnion(leftType, rightType))); + cls.method(sigs::tryArrayUnion, OT_TYPES_GLUE(OperatorTypeHelper::tryArrayUnion(leftType, rightType))); + cls.method(sigs::subtract, OT_BINARY_GLUE(OperatorTypeHelper::subtractOrMultiply(IETR_MINUS, leftType, rightType))); + cls.method(sigs::multiply, OT_BINARY_GLUE(OperatorTypeHelper::subtractOrMultiply(IETR_MUL, leftType, rightType))); + 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::optimizeScalarType, OT_TYPE_GLUE(OperatorTypeHelper::optimizeScalarType(type))); + cls.method(sigs::getNonNegativeIntegerBounds, OT_TYPE_GLUE(OperatorTypeHelper::getNonNegativeIntegerBoundsValue(type))); + cls.method(sigs::computeBitwiseAndRange, OT_TYPES_GLUE(OperatorTypeHelper::computeBitwiseAndRange(leftType, rightType))); + cls.method(sigs::computeBitwiseOrXorRange, OT_TYPES_GLUE(OperatorTypeHelper::computeBitwiseOrXorRange(leftType, rightType))); + + cls.method(sigs::allBitsMask, [](INTERNAL_FUNCTION_PARAMETERS) { + zend_long value; + if (!zp::parse(execute_data, value)) RETURN_THROWS(); + RETURN_LONG(OperatorTypeHelper::allBitsMask(value)); + }); + + 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(); + 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); + }); + + 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(); + PT_RETURN_VAL(OperatorTypeHelper::getTypeFromValue(value)); + }); + + cls.method(sigs::getNeverType, OT_TYPES_GLUE(OperatorTypeHelper::getNeverType(leftType, rightType))); + + cls.method(sigs::negate, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *type, *phpVersion; + if (!zp::parse(execute_data, type, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!otArgType(type, 1) || !otArgPhpVersion(phpVersion, 2))) RETURN_THROWS(); + PT_RETURN_VAL(OperatorTypeHelper::negate(type)); + }); + + cls.method(sigs::intDivide, OT_BINARY_GLUE(OperatorTypeHelper::intDivide(leftType, rightType))); + cls.method(sigs::getIntervals, OT_TYPE_GLUE(OperatorTypeHelper::getIntervalsValue(type))); + + cls.method(sigs::divideIntervals, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *numeratorMin, *numeratorMax, *divisorMin, *divisorMax; + if (!zp::parse(execute_data, numeratorMin, numeratorMax, divisorMin, divisorMax)) RETURN_THROWS(); + OperatorTypeHelper::Bound bounds[4]; + zval *values[4] = { numeratorMin, numeratorMax, divisorMin, divisorMax }; + for (uint32_t i = 0; i < 4; i++) { + if (UNEXPECTED(!OperatorTypeHelper::Bound::fromZval(values[i], i + 1, bounds[i]))) RETURN_THROWS(); + } + PT_RETURN_VAL(OperatorTypeHelper::divideIntervals(bounds[0], bounds[1], bounds[2], bounds[3])); + }); + + cls.method(sigs::divideBounds, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *numerator, *divisor; + if (!zp::parse(execute_data, numerator, divisor)) RETURN_THROWS(); + OperatorTypeHelper::Bound numeratorBound, divisorBound; + if (UNEXPECTED(!OperatorTypeHelper::Bound::fromZval(numerator, 1, numeratorBound) || !OperatorTypeHelper::Bound::fromZval(divisor, 2, divisorBound))) RETURN_THROWS(); + OperatorTypeHelper::divideBounds(numeratorBound, divisorBound).toZval(return_value); + }); + + cls.shadow(&pt_ce_operator_type_helper); +} + +#undef OT_BINARY_GLUE +#undef OT_TYPES_GLUE +#undef OT_TYPE_GLUE + +/* }}} */ diff --git a/turbo-ext/src/OversizedArrayType.cpp b/turbo-ext/src/OversizedArrayType.cpp index e6469f49938..6002aee9578 100644 --- a/turbo-ext/src/OversizedArrayType.cpp +++ b/turbo-ext/src/OversizedArrayType.cpp @@ -232,6 +232,12 @@ static void ZEND_FASTCALL oaError1(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } +static void ZEND_FASTCALL oaError2(INTERNAL_FUNCTION_PARAMETERS) +{ + PT_ARGS(2, 2); + PT_RETURN_VAL(pt_type_new_error_type()); +} + static void ZEND_FASTCALL oaMixed0(INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); @@ -434,7 +440,7 @@ PT_MINIT_REGISTRATION(pt_register_oversized_array_type) cls.method("traverse", reg::Public, 1, { reg::callableArg("cb") }, pt_type_identity_traverse_handler(), &ptret::type); cls.op(PT_OP_TRAVERSE, PT_OP_LAMBDA { return pt_op_traverse_identity(self); }); cls.method(sigs::traverseSimultaneously, oaThis2); - cls.method(sigs::exponentiate, oaError1); + cls.method(sigs::arithmeticPower, oaError2); cls.method(sigs::getFiniteTypes, oaEmptyArray0); cls.method<&OversizedArrayType::getDefaultBaseType>(sigs::getDefaultBaseType); diff --git a/turbo-ext/src/ResourceType.cpp b/turbo-ext/src/ResourceType.cpp index cc9ac8cf710..9c74dd4edb4 100644 --- a/turbo-ext/src/ResourceType.cpp +++ b/turbo-ext/src/ResourceType.cpp @@ -39,9 +39,9 @@ static void ZEND_FASTCALL rtError0(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_type_new_error_type()); } -static void ZEND_FASTCALL rtError1(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL rtError2(INTERNAL_FUNCTION_PARAMETERS) { - PT_ARGS(1, 1); + PT_ARGS(2, 2); PT_RETURN_VAL(pt_type_new_error_type()); } @@ -123,7 +123,7 @@ PT_MINIT_REGISTRATION(pt_register_resource_type) RETURN_COPY_VALUE(&result); }); - cls.method(sigs::exponentiate, rtError1); + cls.method(sigs::arithmeticPower, rtError2); cls.method(sigs::getFiniteTypes, rtEmptyArray0); cls.method(sigs::toPhpDocNode, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/StaticType.cpp b/turbo-ext/src/StaticType.cpp index 3d108cadc06..4db22723232 100644 --- a/turbo-ext/src/StaticType.cpp +++ b/turbo-ext/src/StaticType.cpp @@ -1437,8 +1437,8 @@ PT_MINIT_REGISTRATION(pt_register_static_type) cls.method<&StaticType::tryRemove, zp::Obj>(sigs::tryRemove); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - stDelegate(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC("exponentiate"), 1, 1); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + stDelegate(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC("arithmeticpower"), 2, 2); }); cls.method(sigs::getFiniteTypes, [](INTERNAL_FUNCTION_PARAMETERS) { stDelegate(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC("getfinitetypes"), 0, 0); diff --git a/turbo-ext/src/StrictMixedType.cpp b/turbo-ext/src/StrictMixedType.cpp index bd510ce63fe..46e78aa5c2e 100644 --- a/turbo-ext/src/StrictMixedType.cpp +++ b/turbo-ext/src/StrictMixedType.cpp @@ -415,7 +415,7 @@ PT_MINIT_REGISTRATION(pt_register_strict_mixed_type) PT_ARGS(2, 2); RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); }); - cls.method(sigs::exponentiate, smtError1); + cls.method(sigs::arithmeticPower, smtError2); cls.method(sigs::getFiniteTypes, smtEmptyArray0); cls.method(sigs::toPhpDocNode, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/StringType.cpp b/turbo-ext/src/StringType.cpp index 710ba7454cc..efb8d06e9fb 100644 --- a/turbo-ext/src/StringType.cpp +++ b/turbo-ext/src/StringType.cpp @@ -317,8 +317,9 @@ class StringType static zv::Val getFiniteTypes() { return zv::Val(zv::Arr::empty()); } /* ExponentiateHelper::exponentiate($this, $exponent) */ - zv::Val exponentiate(zval *exponent) const + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const { + (void) phpVersion; zv::Args args{self, exponent}; return pt_type_call_static(PT_CLASS_EXPONENTIATE_HELPER, PT_LC("exponentiate"), 2, args); } @@ -601,7 +602,7 @@ PT_MINIT_REGISTRATION(pt_register_string_type) cls.method<&StringType::getFiniteTypes>(sigs::getFiniteTypes); - cls.method<&StringType::exponentiate, zp::Obj>(sigs::exponentiate); + cls.method<&StringType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method<&StringType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/TemplateArgumentConstraints.cpp b/turbo-ext/src/TemplateArgumentConstraints.cpp index 84567cfc711..9b279b18121 100644 --- a/turbo-ext/src/TemplateArgumentConstraints.cpp +++ b/turbo-ext/src/TemplateArgumentConstraints.cpp @@ -16,12 +16,12 @@ #include "support.h" #include "generated/TemplateArgumentConstraints.h" +#include "TypeTraits.h" #include "generated/UnresolvedTemplateArgumentType.h" namespace slots = ptdecl::TemplateArgumentConstraints::slot; namespace sigs = ptdecl::TemplateArgumentConstraints::sig; #include "zv.h" -#include "TypeTraits.h" #include "Engine.h" #include diff --git a/turbo-ext/src/TemplateArgumentObserver.cpp b/turbo-ext/src/TemplateArgumentObserver.cpp index 265a244c6f9..3e1141319c4 100644 --- a/turbo-ext/src/TemplateArgumentObserver.cpp +++ b/turbo-ext/src/TemplateArgumentObserver.cpp @@ -16,11 +16,11 @@ #include "support.h" #include "generated/TemplateArgumentObserver.h" +#include "TypeTraits.h" #include "generated/UnresolvedTemplateArgumentType.h" namespace sigs = ptdecl::TemplateArgumentObserver::sig; #include "zv.h" -#include "TypeTraits.h" #include "TypeOps.h" #include "Engine.h" #include "AcceptorValues.h" diff --git a/turbo-ext/src/TemplateArgumentResolver.cpp b/turbo-ext/src/TemplateArgumentResolver.cpp index 2f35a62c449..317e28272c7 100644 --- a/turbo-ext/src/TemplateArgumentResolver.cpp +++ b/turbo-ext/src/TemplateArgumentResolver.cpp @@ -15,11 +15,11 @@ #include "support.h" #include "generated/TemplateArgumentResolver.h" +#include "TypeTraits.h" #include "generated/UnresolvedTemplateArgumentType.h" namespace sigs = ptdecl::TemplateArgumentResolver::sig; #include "zv.h" -#include "TypeTraits.h" #include "Engine.h" #include "zend_smart_str.h" diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index 53a76abee9a..e98a5b08685 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -39,6 +39,7 @@ #include "generated/MaybeObjectTypeTrait.h" #include "generated/MaybeStringTypeTrait.h" #include "generated/LateResolvableTypeTrait.h" +#include "generated/OperatorTypeTrait.h" #include "generated/TemplateTypeTrait.h" #pragma GCC diagnostic push @@ -2309,7 +2310,7 @@ void pt_type_trait_array(reg::Class &cls) cls.traitMethod(sigs::isVoid, trinaryNo0); cls.traitOp(PT_OP_IS_VOID, PT_OP_LAMBDA { return pt_op_trinary(PT_TRI_NO); }); cls.traitMethod(sigs::isScalar, trinaryNo0); - cls.traitMethod(sigs::exponentiate, errorType1); + cls.traitMethod(sigs::arithmeticPower, errorType2); cls.traitMethod(sigs::chunkArray, arrayTraitChunkArray); } @@ -3866,7 +3867,7 @@ void pt_type_trait_late_resolvable(reg::Class &cls) if (!zp::parse(execute_data, otherType, phpVersion)) RETURN_THROWS(); PT_RETURN_VAL(PT_LR_THIS.isGreaterThanOrEqual(otherType, phpVersion)); }); - cls.traitMethod(sigs::exponentiate, lrDelegate); + cls.traitMethod(sigs::arithmeticPower, lrDelegate); cls.traitMethod(sigs::getFiniteTypes, lrDelegate); cls.traitMethod(sigs::resolve, lrResolve); cls.traitMethod(sigs::toNativePhpTypeNode, lrDelegate); @@ -5412,3 +5413,50 @@ zv::Val pt_prototype_resolved_property(PrototypeKind kind, const PrototypeTransf } /* }}} */ + +/* {{{ OperatorTypeTrait */ + +/* $this->($right, $phpVersion): OperatorTypeHelper::($this, $right, $phpVersion) */ +template +static void ZEND_FASTCALL operatorBinary(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(pt_operator_type_helper_binary(Op, ZEND_THIS, right, phpVersion)); +} + +void pt_type_trait_operator(reg::Class &cls) +{ + namespace sigs = ptdecl::OperatorTypeTrait::sig; + /* $this->arithmeticPower($exponent, new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION)) */ + cls.traitMethod(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *exponent; + if (!zp::parse(execute_data, exponent)) RETURN_THROWS(); + zval versionId; + ZVAL_LONG(&versionId, PT_PHP_VERSION_FACTORY_MAX_PHP_VERSION); + zv::Val phpVersion = pt_type_new(PT_CLASS_PHP_VERSION, 1, &versionId); + if (UNEXPECTED(phpVersion.isUndef())) RETURN_THROWS(); + zv::Args argv{exponent, phpVersion.raw()}; + PT_RETURN_VAL(pt_type_call(PT_THIS_OBJ, PT_LC("arithmeticpower"), 2, argv)); + }); + cls.traitMethod(sigs::arithmeticAdd, operatorBinary); + cls.traitMethod(sigs::arithmeticSubtract, operatorBinary); + cls.traitMethod(sigs::arithmeticMultiply, operatorBinary); + cls.traitMethod(sigs::arithmeticDivide, operatorBinary); + cls.traitMethod(sigs::arithmeticIntDivide, operatorBinary); + cls.traitMethod(sigs::arithmeticModulo, operatorBinary); + cls.traitMethod(sigs::arithmeticNegate, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *phpVersion; + if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(pt_operator_type_helper_negate(ZEND_THIS, phpVersion)); + }); + cls.traitMethod(sigs::bitwiseAnd, operatorBinary); + cls.traitMethod(sigs::bitwiseOr, operatorBinary); + cls.traitMethod(sigs::bitwiseXor, operatorBinary); + cls.traitMethod(sigs::bitwiseShiftLeft, operatorBinary); + cls.traitMethod(sigs::bitwiseShiftRight, operatorBinary); + cls.traitMethod(sigs::stringConcatenate, operatorBinary); + cls.traitMethod(sigs::arrayUnion, operatorBinary); +} + +/* }}} */ diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 3da29b73dc1..6dc28d7c8da 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -268,11 +268,11 @@ zv::Val pt_integer_range_create_all_greater_than_or_equal_to(zval *value); * (ConstantIntegerType.cpp); false = pending exception */ [[nodiscard]] bool pt_constant_integer_get_value(zend_object *object, zend_long &out); -/* the bodies of IntegerType::looseCompare() and IntegerType::exponentiate() +/* the bodies of IntegerType::looseCompare() and IntegerType::arithmeticPower() * (IntegerType.cpp), for the children's parent:: calls; UNDEF = pending * exception */ zv::Val pt_integer_type_loose_compare(zend_object *self, zval *type, zval *phpVersion); -zv::Val pt_integer_type_exponentiate(zend_object *self, zval *exponent); +zv::Val pt_integer_type_arithmetic_power(zend_object *self, zval *exponent, zval *phpVersion); /* the bodies of StringType's offset methods and tryRemove() (StringType.cpp), * for the children's parent:: calls; the setOffsetValueType() body takes @@ -1022,6 +1022,12 @@ inline constexpr reg::Arg templateTypeStrategy = reg::obj("", ptcls::templateTyp inline constexpr reg::Arg templateTypeVariance = reg::obj("", ptcls::templateTypeVariance); } // namespace ptret +/* src/Type/Traits/OperatorTypeTrait.php — the operator methods, each + * handing $this to OperatorTypeHelper (OperatorTypeHelper.cpp), and the + * deprecated exponentiate() calling $this->arithmeticPower() with the + * newest PhpVersion. */ +void pt_type_trait_operator(reg::Class &cls); + /* src/Type/Generic/TemplateTypeTrait.php — besides the trait's methods the * registrar declares its six private properties (name, scope, strategy, * variance, bound, default — in that order) on the class, as the class's diff --git a/turbo-ext/src/UnionType.cpp b/turbo-ext/src/UnionType.cpp index 35d0387bf55..c0121ec1628 100644 --- a/turbo-ext/src/UnionType.cpp +++ b/turbo-ext/src/UnionType.cpp @@ -2029,7 +2029,11 @@ class UnionType return unionOrSingle(std::move(innerTypes), true); } - zv::Val exponentiate(zval *exponent) const { return unionTypes(UnionMemberOp::call(PT_LC("exponentiate"), 1, exponent)); } + zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const + { + zv::Args args{exponent, phpVersion}; + return unionTypes(UnionMemberOp::call(PT_LC("arithmeticpower"), 2, args)); + } /* $this->finiteTypes ??= the members' finite types uniqued by their * value key (their cache description without one), [] beyond @@ -3464,8 +3468,8 @@ PT_MINIT_REGISTRATION(pt_register_union_type) cls.method(sigs::tryRemove, [](INTERNAL_FUNCTION_PARAMETERS) { pt_ut_value_object(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::tryRemove); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - pt_ut_value_object(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::exponentiate); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + pt_ut_value_two_objects(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::arithmeticPower); }); cls.method(sigs::getFiniteTypes, [](INTERNAL_FUNCTION_PARAMETERS) { pt_ut_value0(INTERNAL_FUNCTION_PARAM_PASSTHRU, &UnionType::getFiniteTypes); diff --git a/turbo-ext/src/UnresolvedTemplateArgumentType.cpp b/turbo-ext/src/UnresolvedTemplateArgumentType.cpp index f65cbb35181..f1541e31ef6 100644 --- a/turbo-ext/src/UnresolvedTemplateArgumentType.cpp +++ b/turbo-ext/src/UnresolvedTemplateArgumentType.cpp @@ -678,7 +678,7 @@ PT_MINIT_REGISTRATION(pt_register_unresolved_template_argument_type) cls.method(sigs::getEnumCaseObject, utaDelegate); cls.op(PT_OP_GET_ENUM_CASE_OBJECT, PT_OP_LAMBDA { return UnresolvedTemplateArgumentType(self).delegateOp(PT_OP_GET_ENUM_CASE_OBJECT, argc, argv); }); cls.method(sigs::getFiniteTypes, utaDelegate); - cls.method(sigs::exponentiate, utaDelegate); + cls.method(sigs::arithmeticPower, utaDelegate); cls.method(sigs::isCallable, utaDelegate); cls.method(sigs::getCallableParametersAcceptors, utaDelegate); cls.method(sigs::isCloneable, utaDelegate); @@ -727,6 +727,7 @@ PT_MINIT_REGISTRATION(pt_register_unresolved_template_argument_type) cls.method(sigs::getReferencedTemplateTypes, utaDelegate); cls.method(sigs::toAbsoluteNumber, utaDelegate); + pt_type_trait_operator(cls); cls.shadow(&pt_ce_unresolved_template_argument_type); } diff --git a/turbo-ext/src/VoidType.cpp b/turbo-ext/src/VoidType.cpp index 8c597e6d5a4..16c9d600c6a 100644 --- a/turbo-ext/src/VoidType.cpp +++ b/turbo-ext/src/VoidType.cpp @@ -125,7 +125,7 @@ class VoidType /* $this */ zv::Val traverseSimultaneously() const { return thisValue(); } - static zv::Val exponentiate() { return pt_type_new_error_type(); } + static zv::Val arithmeticPower() { return pt_type_new_error_type(); } static zv::Val getFiniteTypes() { return zv::Val(zv::Arr::empty()); } @@ -362,10 +362,10 @@ PT_MINIT_REGISTRATION(pt_register_void_type) PT_RETURN_VAL(PT_THIS.traverseSimultaneously()); }); - cls.method(sigs::exponentiate, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *exponent; - if (!zp::parse(execute_data, exponent)) RETURN_THROWS(); - PT_RETURN_VAL(VoidType::exponentiate()); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *exponent, *phpVersion; + if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(VoidType::arithmeticPower()); }); cls.method<&VoidType::getFiniteTypes>(sigs::getFiniteTypes); diff --git a/turbo-ext/src/generated/AccessoryArrayListType.h b/turbo-ext/src/generated/AccessoryArrayListType.h index 75c1c9d8d02..42a72abe195 100644 --- a/turbo-ext/src/generated/AccessoryArrayListType.h +++ b/turbo-ext/src/generated/AccessoryArrayListType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -155,15 +156,15 @@ inline constexpr char strings[] = "right\0" /* 1744 */ "traverseSimultaneously\0" /* 1750 */ "exponent\0" /* 1773 */ - "exponentiate\0" /* 1782 */ - "getFiniteTypes\0" /* 1795 */ - "getDefaultBaseType\0" /* 1810 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1829 */ - "toPhpDocNode\0" /* 1868 */ - "position\0" /* 1881 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1890 */ - "toNativePhpTypeNode\0" /* 1922 */ - "hasTemplateOrLateResolvableType"; /* 1942 */ + "arithmeticPower\0" /* 1782 */ + "getFiniteTypes\0" /* 1798 */ + "getDefaultBaseType\0" /* 1813 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1832 */ + "toPhpDocNode\0" /* 1871 */ + "position\0" /* 1884 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1893 */ + "toNativePhpTypeNode\0" /* 1925 */ + "hasTemplateOrLateResolvableType"; /* 1945 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -294,12 +295,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1744, 0, 132), /* traverseSimultaneously $right */ reg::packed(857, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(12, 0, 132), /* traverseSimultaneously return */ - reg::packed(1773, 0, 132), /* exponentiate $exponent */ - reg::packed(12, 0, 132), /* exponentiate return */ + reg::packed(1773, 0, 132), /* arithmeticPower $exponent */ + reg::packed(1552, 0, 1563), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 132), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 132), /* getDefaultBaseType return */ - reg::packed(12, 0, 1829), /* toPhpDocNode return */ - reg::packed(1881, 0, 1890), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1832), /* toPhpDocNode return */ + reg::packed(1884, 0, 1893), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -396,12 +398,12 @@ inline constexpr sigtab::Sig toArrayKey = { { 1702 /* toArrayKey */, 0, 121, 0, inline constexpr sigtab::Sig toCoercedArgumentType = { { 1713 /* toCoercedArgumentType */, 1, 122, 1, 123, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1735 /* traverse */, 1, 124, 1, 125, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1750 /* traverseSimultaneously */, 2, 126, 2, 128, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1782 /* exponentiate */, 1, 129, 1, 130, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1795 /* getFiniteTypes */, 0, 131, 0, 131, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1810 /* getDefaultBaseType */, 0, 132, 0, 132, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1868 /* toPhpDocNode */, 0, 133, 0, 133, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1922 /* toNativePhpTypeNode */, 1, 134, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1942 /* hasTemplateOrLateResolvableType */, 0, 135, 0, 135, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1782 /* arithmeticPower */, 2, 129, 2, 131, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1798 /* getFiniteTypes */, 0, 132, 0, 132, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1813 /* getDefaultBaseType */, 0, 133, 0, 133, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1871 /* toPhpDocNode */, 0, 134, 0, 134, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1925 /* toNativePhpTypeNode */, 1, 135, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1945 /* hasTemplateOrLateResolvableType */, 0, 136, 0, 136, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryArrayListType diff --git a/turbo-ext/src/generated/AccessoryDecimalIntegerStringType.h b/turbo-ext/src/generated/AccessoryDecimalIntegerStringType.h index 305297fb8f1..f1898f5c466 100644 --- a/turbo-ext/src/generated/AccessoryDecimalIntegerStringType.h +++ b/turbo-ext/src/generated/AccessoryDecimalIntegerStringType.h @@ -33,6 +33,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison_compound(cls); pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -124,15 +125,15 @@ inline constexpr char strings[] = "PHPStan\\Type\\GeneralizePrecision\0" /* 1244 */ "generalize\0" /* 1277 */ "exponent\0" /* 1288 */ - "exponentiate\0" /* 1297 */ - "getFiniteTypes\0" /* 1310 */ - "getDefaultBaseType\0" /* 1325 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1344 */ - "toPhpDocNode\0" /* 1383 */ - "position\0" /* 1396 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1405 */ - "toNativePhpTypeNode\0" /* 1437 */ - "hasTemplateOrLateResolvableType"; /* 1457 */ + "arithmeticPower\0" /* 1297 */ + "getFiniteTypes\0" /* 1313 */ + "getDefaultBaseType\0" /* 1328 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1347 */ + "toPhpDocNode\0" /* 1386 */ + "position\0" /* 1399 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1408 */ + "toNativePhpTypeNode\0" /* 1440 */ + "hasTemplateOrLateResolvableType"; /* 1460 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL, reg::NoString, false, false, 8), /* __construct $inverse */ reg::packed(26, MAY_BE_ARRAY), /* getReferencedClasses return */ @@ -217,12 +218,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(26, 0, 118), /* traverseSimultaneously return */ reg::packed(1234, 0, 1244), /* generalize $precision */ reg::packed(26, 0, 118), /* generalize return */ - reg::packed(1288, 0, 118), /* exponentiate $exponent */ - reg::packed(26, 0, 118), /* exponentiate return */ + reg::packed(1288, 0, 118), /* arithmeticPower $exponent */ + reg::packed(1146, 0, 1157), /* arithmeticPower $phpVersion */ + reg::packed(26, 0, 118), /* arithmeticPower return */ reg::packed(26, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(26, 0, 118), /* getDefaultBaseType return */ - reg::packed(26, 0, 1344), /* toPhpDocNode return */ - reg::packed(1396, 0, 1405), /* toNativePhpTypeNode $position */ + reg::packed(26, 0, 1347), /* toPhpDocNode return */ + reg::packed(1399, 0, 1408), /* toNativePhpTypeNode $position */ reg::packed(26, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -288,12 +290,12 @@ inline constexpr sigtab::Sig looseCompare = { { 1180 /* looseCompare */, 2, 73, inline constexpr sigtab::Sig traverse = { { 1196 /* traverse */, 1, 76, 1, 77, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1211 /* traverseSimultaneously */, 2, 78, 2, 80, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1277 /* generalize */, 1, 81, 1, 82, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1297 /* exponentiate */, 1, 83, 1, 84, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1310 /* getFiniteTypes */, 0, 85, 0, 85, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1325 /* getDefaultBaseType */, 0, 86, 0, 86, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1383 /* toPhpDocNode */, 0, 87, 0, 87, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1437 /* toNativePhpTypeNode */, 1, 88, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1457 /* hasTemplateOrLateResolvableType */, 0, 89, 0, 89, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1297 /* arithmeticPower */, 2, 83, 2, 85, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1313 /* getFiniteTypes */, 0, 86, 0, 86, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1328 /* getDefaultBaseType */, 0, 87, 0, 87, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1386 /* toPhpDocNode */, 0, 88, 0, 88, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1440 /* toNativePhpTypeNode */, 1, 89, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1460 /* hasTemplateOrLateResolvableType */, 0, 90, 0, 90, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryDecimalIntegerStringType diff --git a/turbo-ext/src/generated/AccessoryLiteralStringType.h b/turbo-ext/src/generated/AccessoryLiteralStringType.h index 5ed4d0abd04..d5e691e4317 100644 --- a/turbo-ext/src/generated/AccessoryLiteralStringType.h +++ b/turbo-ext/src/generated/AccessoryLiteralStringType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); pt_type_trait_non_removeable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -115,15 +116,15 @@ inline constexpr char strings[] = "PHPStan\\Type\\GeneralizePrecision\0" /* 1135 */ "generalize\0" /* 1168 */ "exponent\0" /* 1179 */ - "exponentiate\0" /* 1188 */ - "getFiniteTypes\0" /* 1201 */ - "getDefaultBaseType\0" /* 1216 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1235 */ - "toPhpDocNode\0" /* 1274 */ - "position\0" /* 1287 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1296 */ - "toNativePhpTypeNode\0" /* 1328 */ - "hasTemplateOrLateResolvableType"; /* 1348 */ + "arithmeticPower\0" /* 1188 */ + "getFiniteTypes\0" /* 1204 */ + "getDefaultBaseType\0" /* 1219 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1238 */ + "toPhpDocNode\0" /* 1277 */ + "position\0" /* 1290 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1299 */ + "toNativePhpTypeNode\0" /* 1331 */ + "hasTemplateOrLateResolvableType"; /* 1351 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -204,12 +205,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* traverseSimultaneously return */ reg::packed(1125, 0, 1135), /* generalize $precision */ reg::packed(12, 0, 104), /* generalize return */ - reg::packed(1179, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ + reg::packed(1179, 0, 104), /* arithmeticPower $exponent */ + reg::packed(1037, 0, 1048), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 104), /* getDefaultBaseType return */ - reg::packed(12, 0, 1235), /* toPhpDocNode return */ - reg::packed(1287, 0, 1296), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1238), /* toPhpDocNode return */ + reg::packed(1290, 0, 1299), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -273,12 +275,12 @@ inline constexpr sigtab::Sig looseCompare = { { 1071 /* looseCompare */, 2, 69, inline constexpr sigtab::Sig traverse = { { 1087 /* traverse */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1102 /* traverseSimultaneously */, 2, 74, 2, 76, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1168 /* generalize */, 1, 77, 1, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1188 /* exponentiate */, 1, 79, 1, 80, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1201 /* getFiniteTypes */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1216 /* getDefaultBaseType */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1274 /* toPhpDocNode */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1328 /* toNativePhpTypeNode */, 1, 84, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1348 /* hasTemplateOrLateResolvableType */, 0, 85, 0, 85, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1188 /* arithmeticPower */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1204 /* getFiniteTypes */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1219 /* getDefaultBaseType */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1277 /* toPhpDocNode */, 0, 84, 0, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1331 /* toNativePhpTypeNode */, 1, 85, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1351 /* hasTemplateOrLateResolvableType */, 0, 86, 0, 86, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryLiteralStringType diff --git a/turbo-ext/src/generated/AccessoryLowercaseStringType.h b/turbo-ext/src/generated/AccessoryLowercaseStringType.h index 02c1ad2a6e0..540356b5a00 100644 --- a/turbo-ext/src/generated/AccessoryLowercaseStringType.h +++ b/turbo-ext/src/generated/AccessoryLowercaseStringType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); pt_type_trait_non_removeable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -115,15 +116,15 @@ inline constexpr char strings[] = "PHPStan\\Type\\GeneralizePrecision\0" /* 1135 */ "generalize\0" /* 1168 */ "exponent\0" /* 1179 */ - "exponentiate\0" /* 1188 */ - "getFiniteTypes\0" /* 1201 */ - "getDefaultBaseType\0" /* 1216 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1235 */ - "toPhpDocNode\0" /* 1274 */ - "position\0" /* 1287 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1296 */ - "toNativePhpTypeNode\0" /* 1328 */ - "hasTemplateOrLateResolvableType"; /* 1348 */ + "arithmeticPower\0" /* 1188 */ + "getFiniteTypes\0" /* 1204 */ + "getDefaultBaseType\0" /* 1219 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1238 */ + "toPhpDocNode\0" /* 1277 */ + "position\0" /* 1290 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1299 */ + "toNativePhpTypeNode\0" /* 1331 */ + "hasTemplateOrLateResolvableType"; /* 1351 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -204,12 +205,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* traverseSimultaneously return */ reg::packed(1125, 0, 1135), /* generalize $precision */ reg::packed(12, 0, 104), /* generalize return */ - reg::packed(1179, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ + reg::packed(1179, 0, 104), /* arithmeticPower $exponent */ + reg::packed(1037, 0, 1048), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 104), /* getDefaultBaseType return */ - reg::packed(12, 0, 1235), /* toPhpDocNode return */ - reg::packed(1287, 0, 1296), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1238), /* toPhpDocNode return */ + reg::packed(1290, 0, 1299), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -273,12 +275,12 @@ inline constexpr sigtab::Sig looseCompare = { { 1071 /* looseCompare */, 2, 69, inline constexpr sigtab::Sig traverse = { { 1087 /* traverse */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1102 /* traverseSimultaneously */, 2, 74, 2, 76, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1168 /* generalize */, 1, 77, 1, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1188 /* exponentiate */, 1, 79, 1, 80, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1201 /* getFiniteTypes */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1216 /* getDefaultBaseType */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1274 /* toPhpDocNode */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1328 /* toNativePhpTypeNode */, 1, 84, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1348 /* hasTemplateOrLateResolvableType */, 0, 85, 0, 85, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1188 /* arithmeticPower */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1204 /* getFiniteTypes */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1219 /* getDefaultBaseType */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1277 /* toPhpDocNode */, 0, 84, 0, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1331 /* toNativePhpTypeNode */, 1, 85, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1351 /* hasTemplateOrLateResolvableType */, 0, 86, 0, 86, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryLowercaseStringType diff --git a/turbo-ext/src/generated/AccessoryNonEmptyStringType.h b/turbo-ext/src/generated/AccessoryNonEmptyStringType.h index a2dc34d00ae..21c519e4f7b 100644 --- a/turbo-ext/src/generated/AccessoryNonEmptyStringType.h +++ b/turbo-ext/src/generated/AccessoryNonEmptyStringType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); pt_type_trait_undecided_boolean(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -114,15 +115,15 @@ inline constexpr char strings[] = "typeToRemove\0" /* 1148 */ "tryRemove\0" /* 1161 */ "exponent\0" /* 1171 */ - "exponentiate\0" /* 1180 */ - "getFiniteTypes\0" /* 1193 */ - "getDefaultBaseType\0" /* 1208 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1227 */ - "toPhpDocNode\0" /* 1266 */ - "position\0" /* 1279 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1288 */ - "toNativePhpTypeNode\0" /* 1320 */ - "hasTemplateOrLateResolvableType"; /* 1340 */ + "arithmeticPower\0" /* 1180 */ + "getFiniteTypes\0" /* 1196 */ + "getDefaultBaseType\0" /* 1211 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1230 */ + "toPhpDocNode\0" /* 1269 */ + "position\0" /* 1282 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1291 */ + "toNativePhpTypeNode\0" /* 1323 */ + "hasTemplateOrLateResolvableType"; /* 1343 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -202,12 +203,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* generalize return */ reg::packed(1148, 0, 104), /* tryRemove $typeToRemove */ reg::packed(12, MAY_BE_NULL, 104), /* tryRemove return */ - reg::packed(1171, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ + reg::packed(1171, 0, 104), /* arithmeticPower $exponent */ + reg::packed(981, 0, 992), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 104), /* getDefaultBaseType return */ - reg::packed(12, 0, 1227), /* toPhpDocNode return */ - reg::packed(1279, 0, 1288), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1230), /* toPhpDocNode return */ + reg::packed(1282, 0, 1291), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -270,12 +272,12 @@ inline constexpr sigtab::Sig traverse = { { 1056 /* traverse */, 1, 69, 1, 70, Z inline constexpr sigtab::Sig traverseSimultaneously = { { 1071 /* traverseSimultaneously */, 2, 71, 2, 73, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1137 /* generalize */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 1161 /* tryRemove */, 1, 76, 1, 77, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1180 /* exponentiate */, 1, 78, 1, 79, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1193 /* getFiniteTypes */, 0, 80, 0, 80, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1208 /* getDefaultBaseType */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1266 /* toPhpDocNode */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1320 /* toNativePhpTypeNode */, 1, 83, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1340 /* hasTemplateOrLateResolvableType */, 0, 84, 0, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1180 /* arithmeticPower */, 2, 78, 2, 80, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1196 /* getFiniteTypes */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1211 /* getDefaultBaseType */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1269 /* toPhpDocNode */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1323 /* toNativePhpTypeNode */, 1, 84, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1343 /* hasTemplateOrLateResolvableType */, 0, 85, 0, 85, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryNonEmptyStringType diff --git a/turbo-ext/src/generated/AccessoryNonFalsyStringType.h b/turbo-ext/src/generated/AccessoryNonFalsyStringType.h index 4a2a4f1d8e2..b53ac990724 100644 --- a/turbo-ext/src/generated/AccessoryNonFalsyStringType.h +++ b/turbo-ext/src/generated/AccessoryNonFalsyStringType.h @@ -31,6 +31,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); pt_type_trait_non_removeable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -113,15 +114,15 @@ inline constexpr char strings[] = "PHPStan\\Type\\GeneralizePrecision\0" /* 1104 */ "generalize\0" /* 1137 */ "exponent\0" /* 1148 */ - "exponentiate\0" /* 1157 */ - "getFiniteTypes\0" /* 1170 */ - "getDefaultBaseType\0" /* 1185 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1204 */ - "toPhpDocNode\0" /* 1243 */ - "position\0" /* 1256 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1265 */ - "toNativePhpTypeNode\0" /* 1297 */ - "hasTemplateOrLateResolvableType"; /* 1317 */ + "arithmeticPower\0" /* 1157 */ + "getFiniteTypes\0" /* 1173 */ + "getDefaultBaseType\0" /* 1188 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1207 */ + "toPhpDocNode\0" /* 1246 */ + "position\0" /* 1259 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1268 */ + "toNativePhpTypeNode\0" /* 1300 */ + "hasTemplateOrLateResolvableType"; /* 1320 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -199,12 +200,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* traverseSimultaneously return */ reg::packed(1094, 0, 1104), /* generalize $precision */ reg::packed(12, 0, 104), /* generalize return */ - reg::packed(1148, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ + reg::packed(1148, 0, 104), /* arithmeticPower $exponent */ + reg::packed(981, 0, 992), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 104), /* getDefaultBaseType return */ - reg::packed(12, 0, 1204), /* toPhpDocNode return */ - reg::packed(1256, 0, 1265), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1207), /* toPhpDocNode return */ + reg::packed(1259, 0, 1268), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -266,12 +268,12 @@ inline constexpr sigtab::Sig looseCompare = { { 1040 /* looseCompare */, 2, 66, inline constexpr sigtab::Sig traverse = { { 1056 /* traverse */, 1, 69, 1, 70, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1071 /* traverseSimultaneously */, 2, 71, 2, 73, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1137 /* generalize */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1157 /* exponentiate */, 1, 76, 1, 77, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1170 /* getFiniteTypes */, 0, 78, 0, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1185 /* getDefaultBaseType */, 0, 79, 0, 79, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1243 /* toPhpDocNode */, 0, 80, 0, 80, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1297 /* toNativePhpTypeNode */, 1, 81, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1317 /* hasTemplateOrLateResolvableType */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1157 /* arithmeticPower */, 2, 76, 2, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1173 /* getFiniteTypes */, 0, 79, 0, 79, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1188 /* getDefaultBaseType */, 0, 80, 0, 80, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1246 /* toPhpDocNode */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1300 /* toNativePhpTypeNode */, 1, 82, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1320 /* hasTemplateOrLateResolvableType */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryNonFalsyStringType diff --git a/turbo-ext/src/generated/AccessoryNumericStringType.h b/turbo-ext/src/generated/AccessoryNumericStringType.h index 263c4b5fb33..671205dbcfc 100644 --- a/turbo-ext/src/generated/AccessoryNumericStringType.h +++ b/turbo-ext/src/generated/AccessoryNumericStringType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison_compound(cls); pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -114,15 +115,15 @@ inline constexpr char strings[] = "typeToRemove\0" /* 1148 */ "tryRemove\0" /* 1161 */ "exponent\0" /* 1171 */ - "exponentiate\0" /* 1180 */ - "getFiniteTypes\0" /* 1193 */ - "getDefaultBaseType\0" /* 1208 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1227 */ - "toPhpDocNode\0" /* 1266 */ - "position\0" /* 1279 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1288 */ - "toNativePhpTypeNode\0" /* 1320 */ - "hasTemplateOrLateResolvableType"; /* 1340 */ + "arithmeticPower\0" /* 1180 */ + "getFiniteTypes\0" /* 1196 */ + "getDefaultBaseType\0" /* 1211 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1230 */ + "toPhpDocNode\0" /* 1269 */ + "position\0" /* 1282 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1291 */ + "toNativePhpTypeNode\0" /* 1323 */ + "hasTemplateOrLateResolvableType"; /* 1343 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -202,12 +203,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* generalize return */ reg::packed(1148, 0, 104), /* tryRemove $typeToRemove */ reg::packed(12, MAY_BE_NULL, 104), /* tryRemove return */ - reg::packed(1171, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ + reg::packed(1171, 0, 104), /* arithmeticPower $exponent */ + reg::packed(981, 0, 992), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 104), /* getDefaultBaseType return */ - reg::packed(12, 0, 1227), /* toPhpDocNode return */ - reg::packed(1279, 0, 1288), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1230), /* toPhpDocNode return */ + reg::packed(1282, 0, 1291), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -270,12 +272,12 @@ inline constexpr sigtab::Sig traverse = { { 1056 /* traverse */, 1, 69, 1, 70, Z inline constexpr sigtab::Sig traverseSimultaneously = { { 1071 /* traverseSimultaneously */, 2, 71, 2, 73, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1137 /* generalize */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 1161 /* tryRemove */, 1, 76, 1, 77, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1180 /* exponentiate */, 1, 78, 1, 79, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1193 /* getFiniteTypes */, 0, 80, 0, 80, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1208 /* getDefaultBaseType */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1266 /* toPhpDocNode */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1320 /* toNativePhpTypeNode */, 1, 83, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1340 /* hasTemplateOrLateResolvableType */, 0, 84, 0, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1180 /* arithmeticPower */, 2, 78, 2, 80, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1196 /* getFiniteTypes */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1211 /* getDefaultBaseType */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1269 /* toPhpDocNode */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1323 /* toNativePhpTypeNode */, 1, 84, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1343 /* hasTemplateOrLateResolvableType */, 0, 85, 0, 85, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryNumericStringType diff --git a/turbo-ext/src/generated/AccessoryUppercaseStringType.h b/turbo-ext/src/generated/AccessoryUppercaseStringType.h index c84d1028a64..60f9f2ede12 100644 --- a/turbo-ext/src/generated/AccessoryUppercaseStringType.h +++ b/turbo-ext/src/generated/AccessoryUppercaseStringType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); pt_type_trait_non_removeable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -115,15 +116,15 @@ inline constexpr char strings[] = "PHPStan\\Type\\GeneralizePrecision\0" /* 1135 */ "generalize\0" /* 1168 */ "exponent\0" /* 1179 */ - "exponentiate\0" /* 1188 */ - "getFiniteTypes\0" /* 1201 */ - "getDefaultBaseType\0" /* 1216 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1235 */ - "toPhpDocNode\0" /* 1274 */ - "position\0" /* 1287 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1296 */ - "toNativePhpTypeNode\0" /* 1328 */ - "hasTemplateOrLateResolvableType"; /* 1348 */ + "arithmeticPower\0" /* 1188 */ + "getFiniteTypes\0" /* 1204 */ + "getDefaultBaseType\0" /* 1219 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1238 */ + "toPhpDocNode\0" /* 1277 */ + "position\0" /* 1290 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1299 */ + "toNativePhpTypeNode\0" /* 1331 */ + "hasTemplateOrLateResolvableType"; /* 1351 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -204,12 +205,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 104), /* traverseSimultaneously return */ reg::packed(1125, 0, 1135), /* generalize $precision */ reg::packed(12, 0, 104), /* generalize return */ - reg::packed(1179, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ + reg::packed(1179, 0, 104), /* arithmeticPower $exponent */ + reg::packed(1037, 0, 1048), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 104), /* getDefaultBaseType return */ - reg::packed(12, 0, 1235), /* toPhpDocNode return */ - reg::packed(1287, 0, 1296), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1238), /* toPhpDocNode return */ + reg::packed(1290, 0, 1299), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -273,12 +275,12 @@ inline constexpr sigtab::Sig looseCompare = { { 1071 /* looseCompare */, 2, 69, inline constexpr sigtab::Sig traverse = { { 1087 /* traverse */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1102 /* traverseSimultaneously */, 2, 74, 2, 76, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig generalize = { { 1168 /* generalize */, 1, 77, 1, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1188 /* exponentiate */, 1, 79, 1, 80, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1201 /* getFiniteTypes */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1216 /* getDefaultBaseType */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1274 /* toPhpDocNode */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1328 /* toNativePhpTypeNode */, 1, 84, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1348 /* hasTemplateOrLateResolvableType */, 0, 85, 0, 85, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1188 /* arithmeticPower */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1204 /* getFiniteTypes */, 0, 82, 0, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1219 /* getDefaultBaseType */, 0, 83, 0, 83, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1277 /* toPhpDocNode */, 0, 84, 0, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1331 /* toNativePhpTypeNode */, 1, 85, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1351 /* hasTemplateOrLateResolvableType */, 0, 86, 0, 86, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryUppercaseStringType diff --git a/turbo-ext/src/generated/ArrayType.h b/turbo-ext/src/generated/ArrayType.h index a995aff2d34..3c07a2b3f77 100644 --- a/turbo-ext/src/generated/ArrayType.h +++ b/turbo-ext/src/generated/ArrayType.h @@ -34,6 +34,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_array(cls); + pt_type_trait_operator(cls); pt_type_trait_maybe_callable(cls); pt_type_trait_non_object(cls); pt_type_trait_undecided_boolean(cls); diff --git a/turbo-ext/src/generated/ArrayTypeTrait.h b/turbo-ext/src/generated/ArrayTypeTrait.h index 1d5f9f75c59..a4bca7ca647 100644 --- a/turbo-ext/src/generated/ArrayTypeTrait.h +++ b/turbo-ext/src/generated/ArrayTypeTrait.h @@ -53,10 +53,12 @@ inline constexpr char strings[] = "isVoid\0" /* 605 */ "isScalar\0" /* 612 */ "exponent\0" /* 621 */ - "exponentiate\0" /* 630 */ - "lengthType\0" /* 643 */ - "preserveKeys\0" /* 654 */ - "chunkArray"; /* 667 */ + "phpVersion\0" /* 630 */ + "PHPStan\\Php\\PhpVersion\0" /* 641 */ + "arithmeticPower\0" /* 664 */ + "lengthType\0" /* 680 */ + "preserveKeys\0" /* 691 */ + "chunkArray"; /* 704 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 1), /* isArray return */ reg::packed(0, 0, 30), /* toArray return */ @@ -96,10 +98,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 30), /* getObjectTypeOrClassStringObjectType return */ reg::packed(0, 0, 1), /* isVoid return */ reg::packed(0, 0, 1), /* isScalar return */ - reg::packed(621, 0, 30), /* exponentiate $exponent */ - reg::packed(0, 0, 30), /* exponentiate return */ - reg::packed(643, 0, 30), /* chunkArray $lengthType */ - reg::packed(654, 0, 1), /* chunkArray $preserveKeys */ + reg::packed(621, 0, 30), /* arithmeticPower $exponent */ + reg::packed(630, 0, 641), /* arithmeticPower $phpVersion */ + reg::packed(0, 0, 30), /* arithmeticPower return */ + reg::packed(680, 0, 30), /* chunkArray $lengthType */ + reg::packed(691, 0, 1), /* chunkArray $preserveKeys */ reg::packed(0, 0, 30), /* chunkArray return */ }; using Sig = reg::Sig; @@ -144,8 +147,8 @@ inline constexpr sigtab::Sig getClassStringObjectType = { { 543 /* getClassStrin inline constexpr sigtab::Sig getObjectTypeOrClassStringObjectType = { { 568 /* getObjectTypeOrClassStringObjectType */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isVoid = { { 605 /* isVoid */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isScalar = { { 612 /* isScalar */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 630 /* exponentiate */, 1, 38, 1, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig chunkArray = { { 667 /* chunkArray */, 2, 40, 2, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 664 /* arithmeticPower */, 2, 38, 2, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig chunkArray = { { 704 /* chunkArray */, 2, 41, 2, 43, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ArrayTypeTrait diff --git a/turbo-ext/src/generated/BooleanType.h b/turbo-ext/src/generated/BooleanType.h index 4a2e75de9ec..1a3fe1acbc7 100644 --- a/turbo-ext/src/generated/BooleanType.h +++ b/turbo-ext/src/generated/BooleanType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_generic(cls); pt_type_trait_non_offset_accessible(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -72,14 +73,14 @@ inline constexpr char strings[] = "tryRemove\0" /* 435 */ "getFiniteTypes\0" /* 445 */ "exponent\0" /* 460 */ - "exponentiate\0" /* 469 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 482 */ - "toPhpDocNode\0" /* 521 */ - "position\0" /* 534 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 543 */ - "toNativePhpTypeNode\0" /* 575 */ - "toTrinaryLogic\0" /* 595 */ - "hasTemplateOrLateResolvableType"; /* 610 */ + "arithmeticPower\0" /* 469 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 485 */ + "toPhpDocNode\0" /* 524 */ + "position\0" /* 537 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 546 */ + "toNativePhpTypeNode\0" /* 578 */ + "toTrinaryLogic\0" /* 598 */ + "hasTemplateOrLateResolvableType"; /* 613 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getConstantStrings return */ reg::packed(12, MAY_BE_ARRAY), /* getConstantScalarTypes return */ @@ -108,10 +109,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(422, 0, 122), /* tryRemove $typeToRemove */ reg::packed(12, MAY_BE_NULL, 122), /* tryRemove return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(460, 0, 122), /* exponentiate $exponent */ - reg::packed(12, 0, 122), /* exponentiate return */ - reg::packed(12, 0, 482), /* toPhpDocNode return */ - reg::packed(534, 0, 543), /* toNativePhpTypeNode $position */ + reg::packed(460, 0, 122), /* arithmeticPower $exponent */ + reg::packed(350, 0, 361), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 122), /* arithmeticPower return */ + reg::packed(12, 0, 485), /* toPhpDocNode return */ + reg::packed(537, 0, 546), /* toNativePhpTypeNode $position */ reg::packed(12, 0, 263), /* toTrinaryLogic return */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; @@ -143,11 +145,11 @@ inline constexpr sigtab::Sig isScalar = { { 336 /* isScalar */, 0, 20, 0, 20, ZE inline constexpr sigtab::Sig looseCompare = { { 409 /* looseCompare */, 2, 21, 2, 23, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 435 /* tryRemove */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFiniteTypes = { { 445 /* getFiniteTypes */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 469 /* exponentiate */, 1, 27, 1, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 521 /* toPhpDocNode */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 575 /* toNativePhpTypeNode */, 1, 30, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toTrinaryLogic = { { 595 /* toTrinaryLogic */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 610 /* hasTemplateOrLateResolvableType */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 469 /* arithmeticPower */, 2, 27, 2, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 524 /* toPhpDocNode */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 578 /* toNativePhpTypeNode */, 1, 31, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toTrinaryLogic = { { 598 /* toTrinaryLogic */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 613 /* hasTemplateOrLateResolvableType */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::BooleanType diff --git a/turbo-ext/src/generated/CallableType.h b/turbo-ext/src/generated/CallableType.h index e6135eae25e..5e34c4a8c6b 100644 --- a/turbo-ext/src/generated/CallableType.h +++ b/turbo-ext/src/generated/CallableType.h @@ -52,6 +52,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -168,14 +169,14 @@ inline constexpr char strings[] = "getEnumCaseObject\0" /* 1825 */ "isCommonCallable\0" /* 1843 */ "exponent\0" /* 1860 */ - "exponentiate\0" /* 1869 */ - "getFiniteTypes\0" /* 1882 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1897 */ - "toPhpDocNode\0" /* 1936 */ - "position\0" /* 1949 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1958 */ - "toNativePhpTypeNode\0" /* 1990 */ - "hasTemplateOrLateResolvableType"; /* 2010 */ + "arithmeticPower\0" /* 1869 */ + "getFiniteTypes\0" /* 1885 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1900 */ + "toPhpDocNode\0" /* 1939 */ + "position\0" /* 1952 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1961 */ + "toNativePhpTypeNode\0" /* 1993 */ + "hasTemplateOrLateResolvableType"; /* 2013 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 11), /* __construct $parameters */ reg::packed(16, MAY_BE_NULL, 27, false, false, 11), /* __construct $returnType */ @@ -277,11 +278,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(233, MAY_BE_ARRAY), /* getEnumCases return */ reg::packed(233, MAY_BE_NULL, 1788), /* getEnumCaseObject return */ reg::packed(233, MAY_BE_BOOL), /* isCommonCallable return */ - reg::packed(1860, 0, 27), /* exponentiate $exponent */ - reg::packed(233, 0, 27), /* exponentiate return */ + reg::packed(1860, 0, 27), /* arithmeticPower $exponent */ + reg::packed(1703, 0, 1714), /* arithmeticPower $phpVersion */ + reg::packed(233, 0, 27), /* arithmeticPower return */ reg::packed(233, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(233, 0, 1897), /* toPhpDocNode return */ - reg::packed(1949, 0, 1958), /* toNativePhpTypeNode $position */ + reg::packed(233, 0, 1900), /* toPhpDocNode return */ + reg::packed(1952, 0, 1961), /* toNativePhpTypeNode $position */ reg::packed(233, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -361,11 +363,11 @@ inline constexpr sigtab::Sig looseCompare = { { 1762 /* looseCompare */, 2, 94, inline constexpr sigtab::Sig getEnumCases = { { 1775 /* getEnumCases */, 0, 97, 0, 97, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getEnumCaseObject = { { 1825 /* getEnumCaseObject */, 0, 98, 0, 98, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isCommonCallable = { { 1843 /* isCommonCallable */, 0, 99, 0, 99, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1869 /* exponentiate */, 1, 100, 1, 101, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1882 /* getFiniteTypes */, 0, 102, 0, 102, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1936 /* toPhpDocNode */, 0, 103, 0, 103, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1990 /* toNativePhpTypeNode */, 1, 104, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 2010 /* hasTemplateOrLateResolvableType */, 0, 105, 0, 105, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1869 /* arithmeticPower */, 2, 100, 2, 102, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1885 /* getFiniteTypes */, 0, 103, 0, 103, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1939 /* toPhpDocNode */, 0, 104, 0, 104, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1993 /* toNativePhpTypeNode */, 1, 105, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 2013 /* hasTemplateOrLateResolvableType */, 0, 106, 0, 106, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::CallableType diff --git a/turbo-ext/src/generated/ClassConstantAccessType.h b/turbo-ext/src/generated/ClassConstantAccessType.h index ab4465ac63c..a4097eee430 100644 --- a/turbo-ext/src/generated/ClassConstantAccessType.h +++ b/turbo-ext/src/generated/ClassConstantAccessType.h @@ -34,6 +34,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/ClosureType.h b/turbo-ext/src/generated/ClosureType.h index 8db6cad8960..b876716e9dc 100644 --- a/turbo-ext/src/generated/ClosureType.h +++ b/turbo-ext/src/generated/ClosureType.h @@ -69,6 +69,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_offset_accessible(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -245,14 +246,14 @@ inline constexpr char strings[] = "PHPStan\\Php\\PhpVersion\0" /* 3125 */ "looseCompare\0" /* 3148 */ "exponent\0" /* 3161 */ - "exponentiate\0" /* 3170 */ - "getFiniteTypes\0" /* 3183 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3198 */ - "toPhpDocNode\0" /* 3237 */ - "position\0" /* 3250 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3259 */ - "toNativePhpTypeNode\0" /* 3291 */ - "hasTemplateOrLateResolvableType"; /* 3311 */ + "arithmeticPower\0" /* 3170 */ + "getFiniteTypes\0" /* 3186 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3201 */ + "toPhpDocNode\0" /* 3240 */ + "position\0" /* 3253 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3262 */ + "toNativePhpTypeNode\0" /* 3294 */ + "hasTemplateOrLateResolvableType"; /* 3314 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 11), /* __construct $parameters */ reg::packed(16, MAY_BE_NULL, 27, false, false, 11), /* __construct $returnType */ @@ -423,11 +424,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(687, 0, 27), /* looseCompare $type */ reg::packed(3114, 0, 3125), /* looseCompare $phpVersion */ reg::packed(416, 0, 2005), /* looseCompare return */ - reg::packed(3161, 0, 27), /* exponentiate $exponent */ - reg::packed(416, 0, 27), /* exponentiate return */ + reg::packed(3161, 0, 27), /* arithmeticPower $exponent */ + reg::packed(3114, 0, 3125), /* arithmeticPower $phpVersion */ + reg::packed(416, 0, 27), /* arithmeticPower return */ reg::packed(416, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(416, 0, 3198), /* toPhpDocNode return */ - reg::packed(3250, 0, 3259), /* toNativePhpTypeNode $position */ + reg::packed(416, 0, 3201), /* toPhpDocNode return */ + reg::packed(3253, 0, 3262), /* toNativePhpTypeNode $position */ reg::packed(416, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -541,11 +543,11 @@ inline constexpr sigtab::Sig getObjectTypeOrClassStringObjectType = { { 3061 /* inline constexpr sigtab::Sig isVoid = { { 3098 /* isVoid */, 0, 164, 0, 164, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isScalar = { { 3105 /* isScalar */, 0, 165, 0, 165, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig looseCompare = { { 3148 /* looseCompare */, 2, 166, 2, 168, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 3170 /* exponentiate */, 1, 169, 1, 170, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3183 /* getFiniteTypes */, 0, 171, 0, 171, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3237 /* toPhpDocNode */, 0, 172, 0, 172, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3291 /* toNativePhpTypeNode */, 1, 173, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3311 /* hasTemplateOrLateResolvableType */, 0, 174, 0, 174, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 3170 /* arithmeticPower */, 2, 169, 2, 171, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 3186 /* getFiniteTypes */, 0, 172, 0, 172, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3240 /* toPhpDocNode */, 0, 173, 0, 173, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3294 /* toNativePhpTypeNode */, 1, 174, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3314 /* hasTemplateOrLateResolvableType */, 0, 175, 0, 175, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ClosureType diff --git a/turbo-ext/src/generated/ConditionalType.h b/turbo-ext/src/generated/ConditionalType.h index 41330973f91..2274db84304 100644 --- a/turbo-ext/src/generated/ConditionalType.h +++ b/turbo-ext/src/generated/ConditionalType.h @@ -42,6 +42,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/ConditionalTypeForParameter.h b/turbo-ext/src/generated/ConditionalTypeForParameter.h index 313c1a9259d..63ad1496166 100644 --- a/turbo-ext/src/generated/ConditionalTypeForParameter.h +++ b/turbo-ext/src/generated/ConditionalTypeForParameter.h @@ -44,6 +44,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/ConstantArrayType.h b/turbo-ext/src/generated/ConstantArrayType.h index 9e8f7b93ef5..4cb75413b45 100644 --- a/turbo-ext/src/generated/ConstantArrayType.h +++ b/turbo-ext/src/generated/ConstantArrayType.h @@ -56,6 +56,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_array(cls); + pt_type_trait_operator(cls); pt_type_trait_non_object(cls); pt_type_trait_undecided_comparison(cls); } diff --git a/turbo-ext/src/generated/FloatType.h b/turbo-ext/src/generated/FloatType.h index c9486a04e03..29fcfd38d72 100644 --- a/turbo-ext/src/generated/FloatType.h +++ b/turbo-ext/src/generated/FloatType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_offset_accessible(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -97,14 +98,14 @@ inline constexpr char strings[] = "right\0" /* 863 */ "traverseSimultaneously\0" /* 869 */ "exponent\0" /* 892 */ - "exponentiate\0" /* 901 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 914 */ - "toPhpDocNode\0" /* 953 */ - "position\0" /* 966 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 975 */ - "toNativePhpTypeNode\0" /* 1007 */ - "getFiniteTypes\0" /* 1027 */ - "hasTemplateOrLateResolvableType"; /* 1042 */ + "arithmeticPower\0" /* 901 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 917 */ + "toPhpDocNode\0" /* 956 */ + "position\0" /* 969 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 978 */ + "toNativePhpTypeNode\0" /* 1010 */ + "getFiniteTypes\0" /* 1030 */ + "hasTemplateOrLateResolvableType"; /* 1045 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -161,10 +162,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(863, 0, 104), /* traverseSimultaneously $right */ reg::packed(851, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(12, 0, 104), /* traverseSimultaneously return */ - reg::packed(892, 0, 104), /* exponentiate $exponent */ - reg::packed(12, 0, 104), /* exponentiate return */ - reg::packed(12, 0, 914), /* toPhpDocNode return */ - reg::packed(966, 0, 975), /* toNativePhpTypeNode $position */ + reg::packed(892, 0, 104), /* arithmeticPower $exponent */ + reg::packed(779, 0, 790), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 104), /* arithmeticPower return */ + reg::packed(12, 0, 917), /* toPhpDocNode return */ + reg::packed(969, 0, 978), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; @@ -218,11 +220,11 @@ inline constexpr sigtab::Sig isScalar = { { 770 /* isScalar */, 0, 46, 0, 46, ZE inline constexpr sigtab::Sig looseCompare = { { 838 /* looseCompare */, 2, 47, 2, 49, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 854 /* traverse */, 1, 50, 1, 51, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 869 /* traverseSimultaneously */, 2, 52, 2, 54, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 901 /* exponentiate */, 1, 55, 1, 56, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 953 /* toPhpDocNode */, 0, 57, 0, 57, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1007 /* toNativePhpTypeNode */, 1, 58, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1027 /* getFiniteTypes */, 0, 59, 0, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1042 /* hasTemplateOrLateResolvableType */, 0, 60, 0, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 901 /* arithmeticPower */, 2, 55, 2, 57, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 956 /* toPhpDocNode */, 0, 58, 0, 58, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1010 /* toNativePhpTypeNode */, 1, 59, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1030 /* getFiniteTypes */, 0, 60, 0, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1045 /* hasTemplateOrLateResolvableType */, 0, 61, 0, 61, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::FloatType diff --git a/turbo-ext/src/generated/GetTemplateTypeType.h b/turbo-ext/src/generated/GetTemplateTypeType.h index bc3b7091f20..139979069aa 100644 --- a/turbo-ext/src/generated/GetTemplateTypeType.h +++ b/turbo-ext/src/generated/GetTemplateTypeType.h @@ -34,6 +34,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/HasMethodType.h b/turbo-ext/src/generated/HasMethodType.h index bfe02870cad..86cb818e09b 100644 --- a/turbo-ext/src/generated/HasMethodType.h +++ b/turbo-ext/src/generated/HasMethodType.h @@ -39,6 +39,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -110,15 +111,15 @@ inline constexpr char strings[] = "right\0" /* 1052 */ "traverseSimultaneously\0" /* 1058 */ "exponent\0" /* 1081 */ - "exponentiate\0" /* 1090 */ - "getFiniteTypes\0" /* 1103 */ - "getDefaultBaseType\0" /* 1118 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1137 */ - "toPhpDocNode\0" /* 1176 */ - "position\0" /* 1189 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1198 */ - "toNativePhpTypeNode\0" /* 1230 */ - "hasTemplateOrLateResolvableType"; /* 1250 */ + "arithmeticPower\0" /* 1090 */ + "getFiniteTypes\0" /* 1106 */ + "getDefaultBaseType\0" /* 1121 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1140 */ + "toPhpDocNode\0" /* 1179 */ + "position\0" /* 1192 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1201 */ + "toNativePhpTypeNode\0" /* 1233 */ + "hasTemplateOrLateResolvableType"; /* 1253 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_STRING), /* __construct $methodName */ reg::packed(23, MAY_BE_ARRAY), /* getReferencedClasses return */ @@ -182,12 +183,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1052, 0, 91), /* traverseSimultaneously $right */ reg::packed(1040, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(23, 0, 91), /* traverseSimultaneously return */ - reg::packed(1081, 0, 91), /* exponentiate $exponent */ - reg::packed(23, 0, 91), /* exponentiate return */ + reg::packed(1081, 0, 91), /* arithmeticPower $exponent */ + reg::packed(789, 0, 800), /* arithmeticPower $phpVersion */ + reg::packed(23, 0, 91), /* arithmeticPower return */ reg::packed(23, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(23, 0, 91), /* getDefaultBaseType return */ - reg::packed(23, 0, 1137), /* toPhpDocNode return */ - reg::packed(1189, 0, 1198), /* toNativePhpTypeNode $position */ + reg::packed(23, 0, 1140), /* toPhpDocNode return */ + reg::packed(1192, 0, 1201), /* toNativePhpTypeNode $position */ reg::packed(23, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -238,12 +240,12 @@ inline constexpr sigtab::Sig getEnumCases = { { 972 /* getEnumCases */, 0, 55, 0 inline constexpr sigtab::Sig getEnumCaseObject = { { 1022 /* getEnumCaseObject */, 0, 56, 0, 56, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1043 /* traverse */, 1, 57, 1, 58, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1058 /* traverseSimultaneously */, 2, 59, 2, 61, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1090 /* exponentiate */, 1, 62, 1, 63, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1103 /* getFiniteTypes */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1118 /* getDefaultBaseType */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1176 /* toPhpDocNode */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1230 /* toNativePhpTypeNode */, 1, 67, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1250 /* hasTemplateOrLateResolvableType */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1090 /* arithmeticPower */, 2, 62, 2, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1106 /* getFiniteTypes */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1121 /* getDefaultBaseType */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1179 /* toPhpDocNode */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1233 /* toNativePhpTypeNode */, 1, 68, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1253 /* hasTemplateOrLateResolvableType */, 0, 69, 0, 69, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::HasMethodType diff --git a/turbo-ext/src/generated/HasOffsetType.h b/turbo-ext/src/generated/HasOffsetType.h index 8a57e1bfadc..bcc7d7b35fb 100644 --- a/turbo-ext/src/generated/HasOffsetType.h +++ b/turbo-ext/src/generated/HasOffsetType.h @@ -38,6 +38,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -138,15 +139,15 @@ inline constexpr char strings[] = "right\0" /* 1444 */ "traverseSimultaneously\0" /* 1450 */ "exponent\0" /* 1473 */ - "exponentiate\0" /* 1482 */ - "getFiniteTypes\0" /* 1495 */ - "getDefaultBaseType\0" /* 1510 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1529 */ - "toPhpDocNode\0" /* 1568 */ - "position\0" /* 1581 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1590 */ - "toNativePhpTypeNode\0" /* 1622 */ - "hasTemplateOrLateResolvableType"; /* 1642 */ + "arithmeticPower\0" /* 1482 */ + "getFiniteTypes\0" /* 1498 */ + "getDefaultBaseType\0" /* 1513 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1532 */ + "toPhpDocNode\0" /* 1571 */ + "position\0" /* 1584 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1593 */ + "toNativePhpTypeNode\0" /* 1625 */ + "hasTemplateOrLateResolvableType"; /* 1645 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 11), /* __construct $offsetType */ reg::packed(106, 0, 11), /* getOffsetType return */ @@ -251,12 +252,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1444, 0, 193), /* traverseSimultaneously $right */ reg::packed(777, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(106, 0, 193), /* traverseSimultaneously return */ - reg::packed(1473, 0, 193), /* exponentiate $exponent */ - reg::packed(106, 0, 193), /* exponentiate return */ + reg::packed(1473, 0, 193), /* arithmeticPower $exponent */ + reg::packed(1112, 0, 1123), /* arithmeticPower $phpVersion */ + reg::packed(106, 0, 193), /* arithmeticPower return */ reg::packed(106, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(106, 0, 193), /* getDefaultBaseType return */ - reg::packed(106, 0, 1529), /* toPhpDocNode return */ - reg::packed(1581, 0, 1590), /* toNativePhpTypeNode $position */ + reg::packed(106, 0, 1532), /* toPhpDocNode return */ + reg::packed(1584, 0, 1593), /* toNativePhpTypeNode $position */ reg::packed(106, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -328,12 +330,12 @@ inline constexpr sigtab::Sig getEnumCases = { { 1367 /* getEnumCases */, 0, 96, inline constexpr sigtab::Sig getEnumCaseObject = { { 1417 /* getEnumCaseObject */, 0, 97, 0, 97, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1435 /* traverse */, 1, 98, 1, 99, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1450 /* traverseSimultaneously */, 2, 100, 2, 102, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1482 /* exponentiate */, 1, 103, 1, 104, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1495 /* getFiniteTypes */, 0, 105, 0, 105, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1510 /* getDefaultBaseType */, 0, 106, 0, 106, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1568 /* toPhpDocNode */, 0, 107, 0, 107, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1622 /* toNativePhpTypeNode */, 1, 108, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1642 /* hasTemplateOrLateResolvableType */, 0, 109, 0, 109, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1482 /* arithmeticPower */, 2, 103, 2, 105, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1498 /* getFiniteTypes */, 0, 106, 0, 106, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1513 /* getDefaultBaseType */, 0, 107, 0, 107, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1571 /* toPhpDocNode */, 0, 108, 0, 108, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1625 /* toNativePhpTypeNode */, 1, 109, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1645 /* hasTemplateOrLateResolvableType */, 0, 110, 0, 110, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::HasOffsetType diff --git a/turbo-ext/src/generated/HasOffsetValueType.h b/turbo-ext/src/generated/HasOffsetValueType.h index 846dcf1b3d1..ab7b1d30f31 100644 --- a/turbo-ext/src/generated/HasOffsetValueType.h +++ b/turbo-ext/src/generated/HasOffsetValueType.h @@ -39,6 +39,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison_compound(cls); pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -146,15 +147,15 @@ inline constexpr char strings[] = "right\0" /* 1518 */ "traverseSimultaneously\0" /* 1524 */ "exponent\0" /* 1547 */ - "exponentiate\0" /* 1556 */ - "getFiniteTypes\0" /* 1569 */ - "getDefaultBaseType\0" /* 1584 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1603 */ - "toPhpDocNode\0" /* 1642 */ - "position\0" /* 1655 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1664 */ - "toNativePhpTypeNode\0" /* 1696 */ - "hasTemplateOrLateResolvableType"; /* 1716 */ + "arithmeticPower\0" /* 1556 */ + "getFiniteTypes\0" /* 1572 */ + "getDefaultBaseType\0" /* 1587 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1606 */ + "toPhpDocNode\0" /* 1645 */ + "position\0" /* 1658 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1667 */ + "toNativePhpTypeNode\0" /* 1699 */ + "hasTemplateOrLateResolvableType"; /* 1719 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 11), /* __construct $offsetType */ reg::packed(94, 0, 104), /* __construct $valueType */ @@ -267,12 +268,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1518, 0, 104), /* traverseSimultaneously $right */ reg::packed(923, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(134, 0, 104), /* traverseSimultaneously return */ - reg::packed(1547, 0, 104), /* exponentiate $exponent */ - reg::packed(134, 0, 104), /* exponentiate return */ + reg::packed(1547, 0, 104), /* arithmeticPower $exponent */ + reg::packed(1258, 0, 1269), /* arithmeticPower $phpVersion */ + reg::packed(134, 0, 104), /* arithmeticPower return */ reg::packed(134, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(134, 0, 104), /* getDefaultBaseType return */ - reg::packed(134, 0, 1603), /* toPhpDocNode return */ - reg::packed(1655, 0, 1664), /* toNativePhpTypeNode $position */ + reg::packed(134, 0, 1606), /* toPhpDocNode return */ + reg::packed(1658, 0, 1667), /* toNativePhpTypeNode $position */ reg::packed(134, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -348,12 +350,12 @@ inline constexpr sigtab::Sig getEnumCases = { { 1441 /* getEnumCases */, 0, 104, inline constexpr sigtab::Sig getEnumCaseObject = { { 1491 /* getEnumCaseObject */, 0, 105, 0, 105, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1509 /* traverse */, 1, 106, 1, 107, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1524 /* traverseSimultaneously */, 2, 108, 2, 110, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1556 /* exponentiate */, 1, 111, 1, 112, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1569 /* getFiniteTypes */, 0, 113, 0, 113, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1584 /* getDefaultBaseType */, 0, 114, 0, 114, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1642 /* toPhpDocNode */, 0, 115, 0, 115, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1696 /* toNativePhpTypeNode */, 1, 116, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1716 /* hasTemplateOrLateResolvableType */, 0, 117, 0, 117, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1556 /* arithmeticPower */, 2, 111, 2, 113, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1572 /* getFiniteTypes */, 0, 114, 0, 114, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1587 /* getDefaultBaseType */, 0, 115, 0, 115, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1645 /* toPhpDocNode */, 0, 116, 0, 116, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1699 /* toNativePhpTypeNode */, 1, 117, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1719 /* hasTemplateOrLateResolvableType */, 0, 118, 0, 118, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::HasOffsetValueType diff --git a/turbo-ext/src/generated/HasPropertyType.h b/turbo-ext/src/generated/HasPropertyType.h index f962f9a691f..e640f2762cc 100644 --- a/turbo-ext/src/generated/HasPropertyType.h +++ b/turbo-ext/src/generated/HasPropertyType.h @@ -39,6 +39,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -105,15 +106,15 @@ inline constexpr char strings[] = "right\0" /* 882 */ "traverseSimultaneously\0" /* 888 */ "exponent\0" /* 911 */ - "exponentiate\0" /* 920 */ - "getFiniteTypes\0" /* 933 */ - "getDefaultBaseType\0" /* 948 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 967 */ - "toPhpDocNode\0" /* 1006 */ - "position\0" /* 1019 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1028 */ - "toNativePhpTypeNode\0" /* 1060 */ - "hasTemplateOrLateResolvableType"; /* 1080 */ + "arithmeticPower\0" /* 920 */ + "getFiniteTypes\0" /* 936 */ + "getDefaultBaseType\0" /* 951 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 970 */ + "toPhpDocNode\0" /* 1009 */ + "position\0" /* 1022 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1031 */ + "toNativePhpTypeNode\0" /* 1063 */ + "hasTemplateOrLateResolvableType"; /* 1083 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_STRING), /* __construct $propertyName */ reg::packed(25, MAY_BE_ARRAY), /* getReferencedClasses return */ @@ -174,12 +175,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(882, 0, 93), /* traverseSimultaneously $right */ reg::packed(870, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(25, 0, 93), /* traverseSimultaneously return */ - reg::packed(911, 0, 93), /* exponentiate $exponent */ - reg::packed(25, 0, 93), /* exponentiate return */ + reg::packed(911, 0, 93), /* arithmeticPower $exponent */ + reg::packed(619, 0, 630), /* arithmeticPower $phpVersion */ + reg::packed(25, 0, 93), /* arithmeticPower return */ reg::packed(25, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(25, 0, 93), /* getDefaultBaseType return */ - reg::packed(25, 0, 967), /* toPhpDocNode return */ - reg::packed(1019, 0, 1028), /* toNativePhpTypeNode $position */ + reg::packed(25, 0, 970), /* toPhpDocNode return */ + reg::packed(1022, 0, 1031), /* toNativePhpTypeNode $position */ reg::packed(25, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -229,12 +231,12 @@ inline constexpr sigtab::Sig getEnumCases = { { 802 /* getEnumCases */, 0, 52, 0 inline constexpr sigtab::Sig getEnumCaseObject = { { 852 /* getEnumCaseObject */, 0, 53, 0, 53, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 873 /* traverse */, 1, 54, 1, 55, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 888 /* traverseSimultaneously */, 2, 56, 2, 58, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 920 /* exponentiate */, 1, 59, 1, 60, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 933 /* getFiniteTypes */, 0, 61, 0, 61, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 948 /* getDefaultBaseType */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1006 /* toPhpDocNode */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1060 /* toNativePhpTypeNode */, 1, 64, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1080 /* hasTemplateOrLateResolvableType */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 920 /* arithmeticPower */, 2, 59, 2, 61, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 936 /* getFiniteTypes */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 951 /* getDefaultBaseType */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1009 /* toPhpDocNode */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1063 /* toNativePhpTypeNode */, 1, 65, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1083 /* hasTemplateOrLateResolvableType */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::HasPropertyType diff --git a/turbo-ext/src/generated/InitializerExprTypeResolver.h b/turbo-ext/src/generated/InitializerExprTypeResolver.h index 69ddbf4def1..4092c022288 100644 --- a/turbo-ext/src/generated/InitializerExprTypeResolver.h +++ b/turbo-ext/src/generated/InitializerExprTypeResolver.h @@ -96,75 +96,49 @@ inline constexpr char strings[] = "getBitwiseAndType\0" /* 1098 */ "getBitwiseOrType\0" /* 1116 */ "getBitwiseXorType\0" /* 1133 */ - "leftType\0" /* 1151 */ - "rightType\0" /* 1160 */ - "operationCallable\0" /* 1170 */ - "getFiniteOrConstantScalarTypes\0" /* 1188 */ - "getSpaceshipType\0" /* 1219 */ - "getDivType\0" /* 1236 */ - "getDivTypeFromTypes\0" /* 1247 */ - "getModType\0" /* 1267 */ - "divisorMin\0" /* 1278 */ - "divisorMax\0" /* 1289 */ - "getMaxModuloMagnitude\0" /* 1300 */ - "getIntegerBounds\0" /* 1322 */ - "getPlusType\0" /* 1339 */ - "getMinusType\0" /* 1351 */ - "getMulType\0" /* 1364 */ - "getPowType\0" /* 1375 */ - "getShiftLeftType\0" /* 1386 */ - "getShiftRightType\0" /* 1403 */ - "optimizeScalarType\0" /* 1421 */ - "getNonNegativeIntegerBounds\0" /* 1440 */ - "leftNumberType\0" /* 1468 */ - "rightNumberType\0" /* 1483 */ - "computeBitwiseAndRange\0" /* 1499 */ - "computeBitwiseOrXorRange\0" /* 1522 */ - "value\0" /* 1547 */ - "allBitsMask\0" /* 1553 */ - "PHPStan\\Type\\TypeResult\0" /* 1565 */ - "resolveIdenticalType\0" /* 1589 */ - "resolveEqualType\0" /* 1610 */ - "PHPStan\\Type\\Constant\\ConstantArrayType\0" /* 1627 */ - "valueComparisonCallback\0" /* 1667 */ - "resolveConstantArrayTypeComparison\0" /* 1691 */ - "PhpParser\\Node\\Expr\\BinaryOp\0" /* 1726 */ - "resolveCommonMath\0" /* 1755 */ - "range\0" /* 1773 */ - "node\0" /* 1779 */ - "operand\0" /* 1784 */ - "integerRangeMath\0" /* 1792 */ - "shift\0" /* 1809 */ - "shiftLeftOverflows\0" /* 1815 */ - "class\0" /* 1834 */ - "PhpParser\\Node\\Name|PhpParser\\Node\\Expr\0" /* 1840 */ - "constantName\0" /* 1880 */ - "classReflection\0" /* 1893 */ - "PHPStan\\Reflection\\ClassReflection\0" /* 1909 */ - "false\0" /* 1944 */ - "getClassConstFetchTypeByReflection\0" /* 1950 */ - "fetchedOnType\0" /* 1985 */ - "transformStaticType\0" /* 1999 */ - "getClassConstFetchType\0" /* 2019 */ - "getUnaryPlusType\0" /* 2042 */ - "getUnaryMinusType\0" /* 2059 */ - "getUnaryMinusTypeFromType\0" /* 2077 */ - "getBitwiseNotType\0" /* 2103 */ - "getBitwiseNotTypeFromType\0" /* 2121 */ - "name\0" /* 2147 */ - "PhpParser\\Node\\Name\0" /* 2152 */ - "resolveName\0" /* 2172 */ - "PHPStan\\Type\\TypeWithClassName\0" /* 2184 */ - "resolveTypeByName\0" /* 2215 */ - "classType\0" /* 2233 */ - "methodReflectionCandidate\0" /* 2243 */ - "PHPStan\\Reflection\\MethodReflection\0" /* 2269 */ - "resolveTypeByNameWithLateStaticBinding\0" /* 2305 */ - "toIntBound\0" /* 2344 */ - "getTypeFromValue\0" /* 2355 */ - "PHPStan\\Reflection\\ReflectionProvider\0" /* 2372 */ - "getReflectionProvider\0" /* 2410 */ - "getNeverType"; /* 2432 */ + "getSpaceshipType\0" /* 1151 */ + "getDivType\0" /* 1168 */ + "getModType\0" /* 1179 */ + "getPlusType\0" /* 1190 */ + "getMinusType\0" /* 1202 */ + "getMulType\0" /* 1215 */ + "getPowType\0" /* 1226 */ + "getShiftLeftType\0" /* 1237 */ + "getShiftRightType\0" /* 1254 */ + "leftType\0" /* 1272 */ + "rightType\0" /* 1281 */ + "PHPStan\\Type\\TypeResult\0" /* 1291 */ + "resolveIdenticalType\0" /* 1315 */ + "resolveEqualType\0" /* 1336 */ + "PHPStan\\Type\\Constant\\ConstantArrayType\0" /* 1353 */ + "valueComparisonCallback\0" /* 1393 */ + "resolveConstantArrayTypeComparison\0" /* 1417 */ + "class\0" /* 1452 */ + "PhpParser\\Node\\Name|PhpParser\\Node\\Expr\0" /* 1458 */ + "constantName\0" /* 1498 */ + "classReflection\0" /* 1511 */ + "PHPStan\\Reflection\\ClassReflection\0" /* 1527 */ + "false\0" /* 1562 */ + "getClassConstFetchTypeByReflection\0" /* 1568 */ + "fetchedOnType\0" /* 1603 */ + "transformStaticType\0" /* 1617 */ + "getClassConstFetchType\0" /* 1637 */ + "getUnaryPlusType\0" /* 1660 */ + "getUnaryMinusType\0" /* 1677 */ + "getBitwiseNotType\0" /* 1695 */ + "getBitwiseNotTypeFromType\0" /* 1713 */ + "name\0" /* 1739 */ + "PhpParser\\Node\\Name\0" /* 1744 */ + "resolveName\0" /* 1764 */ + "PHPStan\\Type\\TypeWithClassName\0" /* 1776 */ + "resolveTypeByName\0" /* 1807 */ + "classType\0" /* 1825 */ + "methodReflectionCandidate\0" /* 1835 */ + "PHPStan\\Reflection\\MethodReflection\0" /* 1861 */ + "resolveTypeByNameWithLateStaticBinding\0" /* 1897 */ + "PHPStan\\Reflection\\ReflectionProvider\0" /* 1936 */ + "getReflectionProvider\0" /* 1974 */ + "getNeverType"; /* 1996 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 17), /* __construct $constantResolver */ reg::packed(51, 0, 78), /* __construct $reflectionProviderProvider */ @@ -223,10 +197,6 @@ inline constexpr reg::PackedArg args[] = { reg::packed(641, 0, 489), /* getBitwiseXorType $right */ reg::packed(647, MAY_BE_CALLABLE), /* getBitwiseXorType $getTypeCallback */ reg::packed(559, 0, 560), /* getBitwiseXorType return */ - reg::packed(1151, 0, 560), /* getFiniteOrConstantScalarTypes $leftType */ - reg::packed(1160, 0, 560), /* getFiniteOrConstantScalarTypes $rightType */ - reg::packed(1170, MAY_BE_CALLABLE), /* getFiniteOrConstantScalarTypes $operationCallable */ - reg::packed(559, MAY_BE_LONG, 560), /* getFiniteOrConstantScalarTypes return */ reg::packed(636, 0, 489), /* getSpaceshipType $left */ reg::packed(641, 0, 489), /* getSpaceshipType $right */ reg::packed(647, MAY_BE_CALLABLE), /* getSpaceshipType $getTypeCallback */ @@ -235,20 +205,10 @@ inline constexpr reg::PackedArg args[] = { reg::packed(641, 0, 489), /* getDivType $right */ reg::packed(647, MAY_BE_CALLABLE), /* getDivType $getTypeCallback */ reg::packed(559, 0, 560), /* getDivType return */ - reg::packed(636, 0, 489), /* getDivTypeFromTypes $left */ - reg::packed(641, 0, 489), /* getDivTypeFromTypes $right */ - reg::packed(1151, 0, 560), /* getDivTypeFromTypes $leftType */ - reg::packed(1160, 0, 560), /* getDivTypeFromTypes $rightType */ - reg::packed(559, 0, 560), /* getDivTypeFromTypes return */ reg::packed(636, 0, 489), /* getModType $left */ reg::packed(641, 0, 489), /* getModType $right */ reg::packed(647, MAY_BE_CALLABLE), /* getModType $getTypeCallback */ reg::packed(559, 0, 560), /* getModType return */ - reg::packed(1278, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ - reg::packed(1289, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ - reg::packed(559, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude return */ - reg::packed(799, 0, 560), /* getIntegerBounds $type */ - reg::packed(559, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerBounds return */ reg::packed(636, 0, 489), /* getPlusType $left */ reg::packed(641, 0, 489), /* getPlusType $right */ reg::packed(647, MAY_BE_CALLABLE), /* getPlusType $getTypeCallback */ @@ -273,50 +233,27 @@ inline constexpr reg::PackedArg args[] = { reg::packed(641, 0, 489), /* getShiftRightType $right */ reg::packed(647, MAY_BE_CALLABLE), /* getShiftRightType $getTypeCallback */ reg::packed(559, 0, 560), /* getShiftRightType return */ - reg::packed(799, 0, 560), /* optimizeScalarType $type */ - reg::packed(559, 0, 560), /* optimizeScalarType return */ - reg::packed(799, 0, 560), /* getNonNegativeIntegerBounds $type */ - reg::packed(559, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ - reg::packed(1468, 0, 560), /* computeBitwiseAndRange $leftNumberType */ - reg::packed(1483, 0, 560), /* computeBitwiseAndRange $rightNumberType */ - reg::packed(559, MAY_BE_NULL, 560), /* computeBitwiseAndRange return */ - reg::packed(1468, 0, 560), /* computeBitwiseOrXorRange $leftNumberType */ - reg::packed(1483, 0, 560), /* computeBitwiseOrXorRange $rightNumberType */ - reg::packed(559, MAY_BE_NULL, 560), /* computeBitwiseOrXorRange return */ - reg::packed(1547, MAY_BE_LONG), /* allBitsMask $value */ - reg::packed(559, MAY_BE_LONG), /* allBitsMask return */ - reg::packed(1151, 0, 560), /* resolveIdenticalType $leftType */ - reg::packed(1160, 0, 560), /* resolveIdenticalType $rightType */ - reg::packed(559, 0, 1565), /* resolveIdenticalType return */ - reg::packed(1151, 0, 560), /* resolveEqualType $leftType */ - reg::packed(1160, 0, 560), /* resolveEqualType $rightType */ - reg::packed(559, 0, 1565), /* resolveEqualType return */ - reg::packed(1151, 0, 1627), /* resolveConstantArrayTypeComparison $leftType */ - reg::packed(1160, 0, 1627), /* resolveConstantArrayTypeComparison $rightType */ - reg::packed(1667, MAY_BE_CALLABLE), /* resolveConstantArrayTypeComparison $valueComparisonCallback */ - reg::packed(559, 0, 1565), /* resolveConstantArrayTypeComparison return */ - reg::packed(484, 0, 1726), /* resolveCommonMath $expr */ - reg::packed(1151, 0, 560), /* resolveCommonMath $leftType */ - reg::packed(1160, 0, 560), /* resolveCommonMath $rightType */ - reg::packed(559, 0, 560), /* resolveCommonMath return */ - reg::packed(1773, 0, 560), /* integerRangeMath $range */ - reg::packed(1779, 0, 1726), /* integerRangeMath $node */ - reg::packed(1784, 0, 560), /* integerRangeMath $operand */ - reg::packed(559, 0, 560), /* integerRangeMath return */ - reg::packed(1547, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(1809, MAY_BE_LONG), /* shiftLeftOverflows $shift */ - reg::packed(559, MAY_BE_BOOL), /* shiftLeftOverflows return */ - reg::packed(1834, 0, 1840), /* getClassConstFetchTypeByReflection $class */ - reg::packed(1880, MAY_BE_STRING), /* getClassConstFetchTypeByReflection $constantName */ - reg::packed(1893, MAY_BE_NULL, 1909), /* getClassConstFetchTypeByReflection $classReflection */ + reg::packed(1272, 0, 560), /* resolveIdenticalType $leftType */ + reg::packed(1281, 0, 560), /* resolveIdenticalType $rightType */ + reg::packed(559, 0, 1291), /* resolveIdenticalType return */ + reg::packed(1272, 0, 560), /* resolveEqualType $leftType */ + reg::packed(1281, 0, 560), /* resolveEqualType $rightType */ + reg::packed(559, 0, 1291), /* resolveEqualType return */ + reg::packed(1272, 0, 1353), /* resolveConstantArrayTypeComparison $leftType */ + reg::packed(1281, 0, 1353), /* resolveConstantArrayTypeComparison $rightType */ + reg::packed(1393, MAY_BE_CALLABLE), /* resolveConstantArrayTypeComparison $valueComparisonCallback */ + reg::packed(559, 0, 1291), /* resolveConstantArrayTypeComparison return */ + reg::packed(1452, 0, 1458), /* getClassConstFetchTypeByReflection $class */ + reg::packed(1498, MAY_BE_STRING), /* getClassConstFetchTypeByReflection $constantName */ + reg::packed(1511, MAY_BE_NULL, 1527), /* getClassConstFetchTypeByReflection $classReflection */ reg::packed(647, MAY_BE_CALLABLE), /* getClassConstFetchTypeByReflection $getTypeCallback */ - reg::packed(927, MAY_BE_BOOL, reg::NoString, false, false, 1944), /* getClassConstFetchTypeByReflection $nativeTypesPromoted */ + reg::packed(927, MAY_BE_BOOL, reg::NoString, false, false, 1562), /* getClassConstFetchTypeByReflection $nativeTypesPromoted */ reg::packed(559, 0, 560), /* getClassConstFetchTypeByReflection return */ reg::packed(799, 0, 560), /* transformStaticType $type */ - reg::packed(1985, 0, 560), /* transformStaticType $fetchedOnType */ + reg::packed(1603, 0, 560), /* transformStaticType $fetchedOnType */ reg::packed(559, 0, 560), /* transformStaticType return */ - reg::packed(1834, 0, 1840), /* getClassConstFetchType $class */ - reg::packed(1880, MAY_BE_STRING), /* getClassConstFetchType $constantName */ + reg::packed(1452, 0, 1458), /* getClassConstFetchType $class */ + reg::packed(1498, MAY_BE_STRING), /* getClassConstFetchType $constantName */ reg::packed(586, MAY_BE_NULL | MAY_BE_STRING), /* getClassConstFetchType $className */ reg::packed(647, MAY_BE_CALLABLE), /* getClassConstFetchType $getTypeCallback */ reg::packed(559, 0, 560), /* getClassConstFetchType return */ @@ -326,31 +263,24 @@ inline constexpr reg::PackedArg args[] = { reg::packed(484, 0, 489), /* getUnaryMinusType $expr */ reg::packed(647, MAY_BE_CALLABLE), /* getUnaryMinusType $getTypeCallback */ reg::packed(559, 0, 560), /* getUnaryMinusType return */ - reg::packed(484, 0, 489), /* getUnaryMinusTypeFromType $expr */ - reg::packed(799, 0, 560), /* getUnaryMinusTypeFromType $type */ - reg::packed(559, 0, 560), /* getUnaryMinusTypeFromType return */ reg::packed(484, 0, 489), /* getBitwiseNotType $expr */ reg::packed(647, MAY_BE_CALLABLE), /* getBitwiseNotType $getTypeCallback */ reg::packed(559, 0, 560), /* getBitwiseNotType return */ reg::packed(772, 0, 560), /* getBitwiseNotTypeFromType $exprType */ reg::packed(559, 0, 560), /* getBitwiseNotTypeFromType return */ - reg::packed(2147, 0, 2152), /* resolveName $name */ - reg::packed(1893, MAY_BE_NULL, 1909), /* resolveName $classReflection */ + reg::packed(1739, 0, 1744), /* resolveName $name */ + reg::packed(1511, MAY_BE_NULL, 1527), /* resolveName $classReflection */ reg::packed(559, MAY_BE_STRING), /* resolveName return */ - reg::packed(2147, 0, 2152), /* resolveTypeByName $name */ - reg::packed(1893, MAY_BE_NULL, 1909), /* resolveTypeByName $classReflection */ - reg::packed(559, 0, 2184), /* resolveTypeByName return */ - reg::packed(1834, 0, 2152), /* resolveTypeByNameWithLateStaticBinding $class */ - reg::packed(2233, 0, 560), /* resolveTypeByNameWithLateStaticBinding $classType */ - reg::packed(2243, 0, 2269), /* resolveTypeByNameWithLateStaticBinding $methodReflectionCandidate */ + reg::packed(1739, 0, 1744), /* resolveTypeByName $name */ + reg::packed(1511, MAY_BE_NULL, 1527), /* resolveTypeByName $classReflection */ + reg::packed(559, 0, 1776), /* resolveTypeByName return */ + reg::packed(1452, 0, 1744), /* resolveTypeByNameWithLateStaticBinding $class */ + reg::packed(1825, 0, 560), /* resolveTypeByNameWithLateStaticBinding $classType */ + reg::packed(1835, 0, 1861), /* resolveTypeByNameWithLateStaticBinding $methodReflectionCandidate */ reg::packed(559, 0, 560), /* resolveTypeByNameWithLateStaticBinding return */ - reg::packed(1547, MAY_BE_DOUBLE), /* toIntBound $value */ - reg::packed(559, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ - reg::packed(1547, 0), /* getTypeFromValue $value */ - reg::packed(559, 0, 560), /* getTypeFromValue return */ - reg::packed(559, 0, 2372), /* getReflectionProvider return */ - reg::packed(1151, 0, 560), /* getNeverType $leftType */ - reg::packed(1160, 0, 560), /* getNeverType $rightType */ + reg::packed(559, 0, 1936), /* getReflectionProvider return */ + reg::packed(1272, 0, 560), /* getNeverType $leftType */ + reg::packed(1281, 0, 560), /* getNeverType $rightType */ reg::packed(559, 0, 560), /* getNeverType return */ }; using Sig = reg::Sig; @@ -373,45 +303,30 @@ inline constexpr sigtab::Sig createFirstClassCallable = { { 1073 /* createFirstC inline constexpr sigtab::Sig getBitwiseAndType = { { 1098 /* getBitwiseAndType */, 3, 45, 3, 48, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getBitwiseOrType = { { 1116 /* getBitwiseOrType */, 3, 49, 3, 52, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getBitwiseXorType = { { 1133 /* getBitwiseXorType */, 3, 53, 3, 56, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteOrConstantScalarTypes = { { 1188 /* getFiniteOrConstantScalarTypes */, 3, 57, 3, 60, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getSpaceshipType = { { 1219 /* getSpaceshipType */, 3, 61, 3, 64, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDivType = { { 1236 /* getDivType */, 3, 65, 3, 68, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDivTypeFromTypes = { { 1247 /* getDivTypeFromTypes */, 4, 69, 4, 73, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getModType = { { 1267 /* getModType */, 3, 74, 3, 77, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 1300 /* getMaxModuloMagnitude */, 2, 78, 2, 80, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerBounds = { { 1322 /* getIntegerBounds */, 1, 81, 1, 82, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getPlusType = { { 1339 /* getPlusType */, 3, 83, 3, 86, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getMinusType = { { 1351 /* getMinusType */, 3, 87, 3, 90, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getMulType = { { 1364 /* getMulType */, 3, 91, 3, 94, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPowType = { { 1375 /* getPowType */, 3, 95, 3, 98, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getShiftLeftType = { { 1386 /* getShiftLeftType */, 3, 99, 3, 102, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getShiftRightType = { { 1403 /* getShiftRightType */, 3, 103, 3, 106, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 1421 /* optimizeScalarType */, 1, 107, 1, 108, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 1440 /* getNonNegativeIntegerBounds */, 1, 109, 1, 110, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig computeBitwiseAndRange = { { 1499 /* computeBitwiseAndRange */, 2, 111, 2, 113, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 1522 /* computeBitwiseOrXorRange */, 2, 114, 2, 116, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig allBitsMask = { { 1553 /* allBitsMask */, 1, 117, 1, 118, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveIdenticalType = { { 1589 /* resolveIdenticalType */, 2, 119, 2, 121, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveEqualType = { { 1610 /* resolveEqualType */, 2, 122, 2, 124, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveConstantArrayTypeComparison = { { 1691 /* resolveConstantArrayTypeComparison */, 3, 125, 3, 128, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 1755 /* resolveCommonMath */, 3, 129, 3, 132, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig integerRangeMath = { { 1792 /* integerRangeMath */, 3, 133, 3, 136, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 1815 /* shiftLeftOverflows */, 2, 137, 2, 139, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getClassConstFetchTypeByReflection = { { 1950 /* getClassConstFetchTypeByReflection */, 4, 140, 5, 145, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig transformStaticType = { { 1999 /* transformStaticType */, 2, 146, 2, 148, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getClassConstFetchType = { { 2019 /* getClassConstFetchType */, 4, 149, 4, 153, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getUnaryPlusType = { { 2042 /* getUnaryPlusType */, 2, 154, 2, 156, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getUnaryMinusType = { { 2059 /* getUnaryMinusType */, 2, 157, 2, 159, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getUnaryMinusTypeFromType = { { 2077 /* getUnaryMinusTypeFromType */, 2, 160, 2, 162, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getBitwiseNotType = { { 2103 /* getBitwiseNotType */, 2, 163, 2, 165, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getBitwiseNotTypeFromType = { { 2121 /* getBitwiseNotTypeFromType */, 1, 166, 1, 167, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveName = { { 2172 /* resolveName */, 2, 168, 2, 170, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig resolveTypeByName = { { 2215 /* resolveTypeByName */, 2, 171, 2, 173, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig resolveTypeByNameWithLateStaticBinding = { { 2305 /* resolveTypeByNameWithLateStaticBinding */, 3, 174, 3, 177, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toIntBound = { { 2344 /* toIntBound */, 1, 178, 1, 179, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 2355 /* getTypeFromValue */, 1, 180, 1, 181, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getReflectionProvider = { { 2410 /* getReflectionProvider */, 0, 182, 0, 182, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getNeverType = { { 2432 /* getNeverType */, 2, 183, 2, 185, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getSpaceshipType = { { 1151 /* getSpaceshipType */, 3, 57, 3, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDivType = { { 1168 /* getDivType */, 3, 61, 3, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getModType = { { 1179 /* getModType */, 3, 65, 3, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPlusType = { { 1190 /* getPlusType */, 3, 69, 3, 72, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMinusType = { { 1202 /* getMinusType */, 3, 73, 3, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMulType = { { 1215 /* getMulType */, 3, 77, 3, 80, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPowType = { { 1226 /* getPowType */, 3, 81, 3, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getShiftLeftType = { { 1237 /* getShiftLeftType */, 3, 85, 3, 88, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getShiftRightType = { { 1254 /* getShiftRightType */, 3, 89, 3, 92, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveIdenticalType = { { 1315 /* resolveIdenticalType */, 2, 93, 2, 95, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveEqualType = { { 1336 /* resolveEqualType */, 2, 96, 2, 98, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveConstantArrayTypeComparison = { { 1417 /* resolveConstantArrayTypeComparison */, 3, 99, 3, 102, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClassConstFetchTypeByReflection = { { 1568 /* getClassConstFetchTypeByReflection */, 4, 103, 5, 108, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig transformStaticType = { { 1617 /* transformStaticType */, 2, 109, 2, 111, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClassConstFetchType = { { 1637 /* getClassConstFetchType */, 4, 112, 4, 116, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getUnaryPlusType = { { 1660 /* getUnaryPlusType */, 2, 117, 2, 119, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getUnaryMinusType = { { 1677 /* getUnaryMinusType */, 2, 120, 2, 122, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getBitwiseNotType = { { 1695 /* getBitwiseNotType */, 2, 123, 2, 125, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getBitwiseNotTypeFromType = { { 1713 /* getBitwiseNotTypeFromType */, 1, 126, 1, 127, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveName = { { 1764 /* resolveName */, 2, 128, 2, 130, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig resolveTypeByName = { { 1807 /* resolveTypeByName */, 2, 131, 2, 133, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig resolveTypeByNameWithLateStaticBinding = { { 1897 /* resolveTypeByNameWithLateStaticBinding */, 3, 134, 3, 137, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getReflectionProvider = { { 1974 /* getReflectionProvider */, 0, 138, 0, 138, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getNeverType = { { 1996 /* getNeverType */, 2, 139, 2, 141, ZEND_ACC_PRIVATE } }; } // namespace sig } // namespace ptdecl::InitializerExprTypeResolver diff --git a/turbo-ext/src/generated/IntegerRangeType.h b/turbo-ext/src/generated/IntegerRangeType.h index a2d7db5f18b..a439446e031 100644 --- a/turbo-ext/src/generated/IntegerRangeType.h +++ b/turbo-ext/src/generated/IntegerRangeType.h @@ -27,6 +27,12 @@ inline void declareProperties(reg::Class &cls) cls.property("max", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL | MAY_BE_LONG); } +/* the shared registrars of the traits the twin uses, in its own order (a used trait's own traits after it) */ +inline void registerTraits(reg::Class &cls) +{ + pt_type_trait_operator(cls); +} + /* the string and parameter tables the signatures below index into (see reg::Sig) */ namespace sigtab { inline constexpr char strings[] = @@ -93,11 +99,11 @@ inline constexpr char strings[] = "typeToRemove\0" /* 845 */ "tryRemove\0" /* 858 */ "exponent\0" /* 868 */ - "exponentiate\0" /* 877 */ - "getFiniteTypes\0" /* 890 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 905 */ - "toPhpDocNode\0" /* 944 */ - "looseCompare"; /* 957 */ + "arithmeticPower\0" /* 877 */ + "getFiniteTypes\0" /* 893 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 908 */ + "toPhpDocNode\0" /* 947 */ + "looseCompare"; /* 960 */ 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 */ @@ -171,10 +177,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(28, MAY_BE_NULL, 29), /* tryIntersect return */ reg::packed(845, 0, 29), /* tryRemove $typeToRemove */ reg::packed(28, MAY_BE_NULL, 29), /* tryRemove return */ - reg::packed(868, 0, 29), /* exponentiate $exponent */ - reg::packed(28, 0, 29), /* exponentiate return */ + reg::packed(868, 0, 29), /* arithmeticPower $exponent */ + reg::packed(546, 0, 557), /* arithmeticPower $phpVersion */ + reg::packed(28, 0, 29), /* arithmeticPower return */ reg::packed(28, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(28, 0, 905), /* toPhpDocNode return */ + reg::packed(28, 0, 908), /* toPhpDocNode return */ reg::packed(287, 0, 29), /* looseCompare $type */ reg::packed(546, 0, 557), /* looseCompare $phpVersion */ reg::packed(28, 0, 745), /* looseCompare return */ @@ -217,10 +224,10 @@ inline constexpr sigtab::Sig toString = { { 814 /* toString */, 0, 65, 0, 65, ZE inline constexpr sigtab::Sig tryUnion = { { 823 /* tryUnion */, 1, 66, 1, 67, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryIntersect = { { 832 /* tryIntersect */, 1, 68, 1, 69, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 858 /* tryRemove */, 1, 70, 1, 71, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 877 /* exponentiate */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 890 /* getFiniteTypes */, 0, 74, 0, 74, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 944 /* toPhpDocNode */, 0, 75, 0, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 957 /* looseCompare */, 2, 76, 2, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 877 /* arithmeticPower */, 2, 72, 2, 74, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 893 /* getFiniteTypes */, 0, 75, 0, 75, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 947 /* toPhpDocNode */, 0, 76, 0, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 960 /* looseCompare */, 2, 77, 2, 79, 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 6ce2ddc264e..7233a0b2136 100644 --- a/turbo-ext/src/generated/IntegerType.h +++ b/turbo-ext/src/generated/IntegerType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_generic(cls); pt_type_trait_non_offset_accessible(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -72,13 +73,13 @@ inline constexpr char strings[] = "tryRemove\0" /* 406 */ "getFiniteTypes\0" /* 416 */ "exponent\0" /* 431 */ - "exponentiate\0" /* 440 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 453 */ - "toPhpDocNode\0" /* 492 */ - "position\0" /* 505 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 514 */ - "toNativePhpTypeNode\0" /* 546 */ - "hasTemplateOrLateResolvableType"; /* 566 */ + "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 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ @@ -107,10 +108,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(393, 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), /* exponentiate $exponent */ - reg::packed(46, 0, 75), /* exponentiate return */ - reg::packed(46, 0, 453), /* toPhpDocNode return */ - reg::packed(505, 0, 514), /* toNativePhpTypeNode $position */ + reg::packed(431, 0, 75), /* arithmeticPower $exponent */ + reg::packed(321, 0, 332), /* 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, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -141,10 +143,10 @@ inline constexpr sigtab::Sig isScalar = { { 307 /* isScalar */, 0, 20, 0, 20, ZE 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 exponentiate = { { 440 /* exponentiate */, 1, 27, 1, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 492 /* toPhpDocNode */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 546 /* toNativePhpTypeNode */, 1, 30, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 566 /* hasTemplateOrLateResolvableType */, 0, 31, 0, 31, 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 } }; } // namespace sig } // namespace ptdecl::IntegerType diff --git a/turbo-ext/src/generated/IntersectionType.h b/turbo-ext/src/generated/IntersectionType.h index f6da2342672..8b7eea42d6b 100644 --- a/turbo-ext/src/generated/IntersectionType.h +++ b/turbo-ext/src/generated/IntersectionType.h @@ -61,6 +61,7 @@ inline void registerTraits(reg::Class &cls) { pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -264,28 +265,28 @@ inline constexpr char strings[] = "typeToRemove\0" /* 3412 */ "tryRemove\0" /* 3425 */ "exponent\0" /* 3435 */ - "exponentiate\0" /* 3444 */ - "getFiniteTypes\0" /* 3457 */ - "finiteTypes\0" /* 3472 */ - "indexFiniteTypes\0" /* 3484 */ - "indexed\0" /* 3501 */ - "key\0" /* 3509 */ - "finiteType\0" /* 3513 */ - "hasFiniteType\0" /* 3524 */ - "getResult\0" /* 3538 */ - "filter\0" /* 3548 */ - "intersectResults\0" /* 3555 */ - "getType\0" /* 3572 */ - "intersectTypes\0" /* 3580 */ - "operands\0" /* 3595 */ - "intersectOperands\0" /* 3604 */ - "intersectTypesPreserveTemplateType\0" /* 3622 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3657 */ - "toPhpDocNode\0" /* 3696 */ - "position\0" /* 3709 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3718 */ - "toNativePhpTypeNode\0" /* 3750 */ - "hasTemplateOrLateResolvableType"; /* 3770 */ + "arithmeticPower\0" /* 3444 */ + "getFiniteTypes\0" /* 3460 */ + "finiteTypes\0" /* 3475 */ + "indexFiniteTypes\0" /* 3487 */ + "indexed\0" /* 3504 */ + "key\0" /* 3512 */ + "finiteType\0" /* 3516 */ + "hasFiniteType\0" /* 3527 */ + "getResult\0" /* 3541 */ + "filter\0" /* 3551 */ + "intersectResults\0" /* 3558 */ + "getType\0" /* 3575 */ + "intersectTypes\0" /* 3583 */ + "operands\0" /* 3598 */ + "intersectOperands\0" /* 3607 */ + "intersectTypesPreserveTemplateType\0" /* 3625 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3660 */ + "toPhpDocNode\0" /* 3699 */ + "position\0" /* 3712 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3721 */ + "toNativePhpTypeNode\0" /* 3753 */ + "hasTemplateOrLateResolvableType"; /* 3773 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $types */ reg::packed(18, MAY_BE_ARRAY), /* getTypes return */ @@ -519,26 +520,27 @@ inline constexpr reg::PackedArg args[] = { reg::packed(18, 0, 56), /* traverseSimultaneously return */ reg::packed(3412, 0, 56), /* tryRemove $typeToRemove */ reg::packed(18, MAY_BE_NULL, 56), /* tryRemove return */ - reg::packed(3435, 0, 56), /* exponentiate $exponent */ - reg::packed(18, 0, 56), /* exponentiate return */ + reg::packed(3435, 0, 56), /* arithmeticPower $exponent */ + reg::packed(1786, 0, 1797), /* arithmeticPower $phpVersion */ + reg::packed(18, 0, 56), /* arithmeticPower return */ reg::packed(18, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(3472, MAY_BE_ARRAY), /* indexFiniteTypes $finiteTypes */ + reg::packed(3475, MAY_BE_ARRAY), /* indexFiniteTypes $finiteTypes */ reg::packed(18, MAY_BE_ARRAY), /* indexFiniteTypes return */ - reg::packed(3501, MAY_BE_ARRAY), /* hasFiniteType $indexed */ - reg::packed(3509, MAY_BE_NULL | MAY_BE_STRING), /* hasFiniteType $key */ - reg::packed(3513, 0, 56), /* hasFiniteType $finiteType */ + reg::packed(3504, MAY_BE_ARRAY), /* hasFiniteType $indexed */ + reg::packed(3512, MAY_BE_NULL | MAY_BE_STRING), /* hasFiniteType $key */ + reg::packed(3516, 0, 56), /* hasFiniteType $finiteType */ reg::packed(18, MAY_BE_BOOL), /* hasFiniteType return */ - reg::packed(3538, MAY_BE_CALLABLE), /* intersectResults $getResult */ - reg::packed(3548, MAY_BE_NULL | MAY_BE_CALLABLE, reg::NoString, false, false, 2271), /* intersectResults $filter */ + reg::packed(3541, MAY_BE_CALLABLE), /* intersectResults $getResult */ + reg::packed(3551, MAY_BE_NULL | MAY_BE_CALLABLE, reg::NoString, false, false, 2271), /* intersectResults $filter */ reg::packed(18, 0, 542), /* intersectResults return */ - reg::packed(3572, MAY_BE_CALLABLE), /* intersectTypes $getType */ + reg::packed(3575, MAY_BE_CALLABLE), /* intersectTypes $getType */ reg::packed(18, 0, 56), /* intersectTypes return */ - reg::packed(3595, MAY_BE_ARRAY), /* intersectOperands $operands */ + reg::packed(3598, MAY_BE_ARRAY), /* intersectOperands $operands */ reg::packed(18, 0, 56), /* intersectOperands return */ - reg::packed(3572, MAY_BE_CALLABLE), /* intersectTypesPreserveTemplateType $getType */ + reg::packed(3575, MAY_BE_CALLABLE), /* intersectTypesPreserveTemplateType $getType */ reg::packed(18, 0, 56), /* intersectTypesPreserveTemplateType return */ - reg::packed(18, 0, 3657), /* toPhpDocNode return */ - reg::packed(3709, 0, 3718), /* toNativePhpTypeNode $position */ + reg::packed(18, 0, 3660), /* toPhpDocNode return */ + reg::packed(3712, 0, 3721), /* toNativePhpTypeNode $position */ reg::packed(18, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -685,17 +687,17 @@ inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3347 /* getReferen inline constexpr sigtab::Sig traverse = { { 3374 /* traverse */, 1, 225, 1, 226, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 3389 /* traverseSimultaneously */, 2, 227, 2, 229, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 3425 /* tryRemove */, 1, 230, 1, 231, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 3444 /* exponentiate */, 1, 232, 1, 233, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3457 /* getFiniteTypes */, 0, 234, 0, 234, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig indexFiniteTypes = { { 3484 /* indexFiniteTypes */, 1, 235, 1, 236, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig hasFiniteType = { { 3524 /* hasFiniteType */, 3, 237, 3, 240, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intersectResults = { { 3555 /* intersectResults */, 1, 241, 2, 243, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig intersectTypes = { { 3580 /* intersectTypes */, 1, 244, 1, 245, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig intersectOperands = { { 3604 /* intersectOperands */, 1, 246, 1, 247, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intersectTypesPreserveTemplateType = { { 3622 /* intersectTypesPreserveTemplateType */, 1, 248, 1, 249, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3696 /* toPhpDocNode */, 0, 250, 0, 250, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3750 /* toNativePhpTypeNode */, 1, 251, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3770 /* hasTemplateOrLateResolvableType */, 0, 252, 0, 252, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 3444 /* arithmeticPower */, 2, 232, 2, 234, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 3460 /* getFiniteTypes */, 0, 235, 0, 235, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig indexFiniteTypes = { { 3487 /* indexFiniteTypes */, 1, 236, 1, 237, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig hasFiniteType = { { 3527 /* hasFiniteType */, 3, 238, 3, 241, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intersectResults = { { 3558 /* intersectResults */, 1, 242, 2, 244, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig intersectTypes = { { 3583 /* intersectTypes */, 1, 245, 1, 246, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig intersectOperands = { { 3607 /* intersectOperands */, 1, 247, 1, 248, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intersectTypesPreserveTemplateType = { { 3625 /* intersectTypesPreserveTemplateType */, 1, 249, 1, 250, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3699 /* toPhpDocNode */, 0, 251, 0, 251, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3753 /* toNativePhpTypeNode */, 1, 252, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3773 /* hasTemplateOrLateResolvableType */, 0, 253, 0, 253, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntersectionType diff --git a/turbo-ext/src/generated/IterableType.h b/turbo-ext/src/generated/IterableType.h index 53d8202f93c..c808c2f0c0d 100644 --- a/turbo-ext/src/generated/IterableType.h +++ b/turbo-ext/src/generated/IterableType.h @@ -37,6 +37,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison_compound(cls); pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -136,14 +137,14 @@ inline constexpr char strings[] = "typeToRemove\0" /* 1466 */ "tryRemove\0" /* 1479 */ "exponent\0" /* 1489 */ - "exponentiate\0" /* 1498 */ - "getFiniteTypes\0" /* 1511 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1526 */ - "toPhpDocNode\0" /* 1565 */ - "position\0" /* 1578 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1587 */ - "toNativePhpTypeNode\0" /* 1619 */ - "hasTemplateOrLateResolvableType"; /* 1639 */ + "arithmeticPower\0" /* 1498 */ + "getFiniteTypes\0" /* 1514 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1529 */ + "toPhpDocNode\0" /* 1568 */ + "position\0" /* 1581 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1590 */ + "toNativePhpTypeNode\0" /* 1622 */ + "hasTemplateOrLateResolvableType"; /* 1642 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 8), /* __construct $keyType */ reg::packed(26, 0, 8), /* __construct $itemType */ @@ -233,11 +234,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(47, 0, 8), /* traverseSimultaneously return */ reg::packed(1466, 0, 8), /* tryRemove $typeToRemove */ reg::packed(47, MAY_BE_NULL, 8), /* tryRemove return */ - reg::packed(1489, 0, 8), /* exponentiate $exponent */ - reg::packed(47, 0, 8), /* exponentiate return */ + reg::packed(1489, 0, 8), /* arithmeticPower $exponent */ + reg::packed(1130, 0, 1141), /* arithmeticPower $phpVersion */ + reg::packed(47, 0, 8), /* arithmeticPower return */ reg::packed(47, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(47, 0, 1526), /* toPhpDocNode return */ - reg::packed(1578, 0, 1587), /* toNativePhpTypeNode $position */ + reg::packed(47, 0, 1529), /* toPhpDocNode return */ + reg::packed(1581, 0, 1590), /* toNativePhpTypeNode $position */ reg::packed(47, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -311,11 +313,11 @@ inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 1398 /* getReferen inline constexpr sigtab::Sig traverse = { { 1428 /* traverse */, 1, 81, 1, 82, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1443 /* traverseSimultaneously */, 2, 83, 2, 85, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 1479 /* tryRemove */, 1, 86, 1, 87, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1498 /* exponentiate */, 1, 88, 1, 89, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1511 /* getFiniteTypes */, 0, 90, 0, 90, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1565 /* toPhpDocNode */, 0, 91, 0, 91, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1619 /* toNativePhpTypeNode */, 1, 92, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1639 /* hasTemplateOrLateResolvableType */, 0, 93, 0, 93, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1498 /* arithmeticPower */, 2, 88, 2, 90, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1514 /* getFiniteTypes */, 0, 91, 0, 91, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1568 /* toPhpDocNode */, 0, 92, 0, 92, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1622 /* toNativePhpTypeNode */, 1, 93, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1642 /* hasTemplateOrLateResolvableType */, 0, 94, 0, 94, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IterableType diff --git a/turbo-ext/src/generated/KeyOfType.h b/turbo-ext/src/generated/KeyOfType.h index 64331aef218..addb714e897 100644 --- a/turbo-ext/src/generated/KeyOfType.h +++ b/turbo-ext/src/generated/KeyOfType.h @@ -29,6 +29,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/LateResolvableArrayShapeType.h b/turbo-ext/src/generated/LateResolvableArrayShapeType.h index a6e2e622cf6..ca0c7fbb6c5 100644 --- a/turbo-ext/src/generated/LateResolvableArrayShapeType.h +++ b/turbo-ext/src/generated/LateResolvableArrayShapeType.h @@ -34,6 +34,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/LateResolvableTypeTrait.h b/turbo-ext/src/generated/LateResolvableTypeTrait.h index 82ec1a45bc6..e11ba5a53b6 100644 --- a/turbo-ext/src/generated/LateResolvableTypeTrait.h +++ b/turbo-ext/src/generated/LateResolvableTypeTrait.h @@ -186,14 +186,14 @@ inline constexpr char strings[] = "isGreaterThan\0" /* 3008 */ "isGreaterThanOrEqual\0" /* 3022 */ "exponent\0" /* 3043 */ - "exponentiate\0" /* 3052 */ - "getFiniteTypes\0" /* 3065 */ - "resolve\0" /* 3080 */ - "getResult\0" /* 3088 */ - "position\0" /* 3098 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3107 */ - "toNativePhpTypeNode\0" /* 3139 */ - "hasTemplateOrLateResolvableType"; /* 3159 */ + "arithmeticPower\0" /* 3052 */ + "getFiniteTypes\0" /* 3068 */ + "resolve\0" /* 3083 */ + "getResult\0" /* 3091 */ + "position\0" /* 3101 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3110 */ + "toNativePhpTypeNode\0" /* 3142 */ + "hasTemplateOrLateResolvableType"; /* 3162 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* getObjectClassNames return */ reg::packed(0, MAY_BE_ARRAY), /* getObjectClassReflections return */ @@ -400,12 +400,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(2350, 0, 99), /* isGreaterThanOrEqual $otherType */ reg::packed(2360, 0, 2371), /* isGreaterThanOrEqual $phpVersion */ reg::packed(0, 0, 283), /* isGreaterThanOrEqual return */ - reg::packed(3043, 0, 99), /* exponentiate $exponent */ - reg::packed(0, 0, 99), /* exponentiate return */ + reg::packed(3043, 0, 99), /* arithmeticPower $exponent */ + reg::packed(2360, 0, 2371), /* arithmeticPower $phpVersion */ + reg::packed(0, 0, 99), /* arithmeticPower return */ reg::packed(0, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(0, 0, 99), /* resolve return */ reg::packed(0, 0, 99), /* getResult return */ - reg::packed(3098, 0, 3107), /* toNativePhpTypeNode $position */ + reg::packed(3101, 0, 3110), /* toNativePhpTypeNode $position */ reg::packed(0, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -538,12 +539,12 @@ inline constexpr sigtab::Sig isSubTypeOf = { { 2969 /* isSubTypeOf */, 1, 194, 1 inline constexpr sigtab::Sig isAcceptedBy = { { 2995 /* isAcceptedBy */, 2, 196, 2, 198, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isGreaterThan = { { 3008 /* isGreaterThan */, 2, 199, 2, 201, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isGreaterThanOrEqual = { { 3022 /* isGreaterThanOrEqual */, 2, 202, 2, 204, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 3052 /* exponentiate */, 1, 205, 1, 206, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3065 /* getFiniteTypes */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolve = { { 3080 /* resolve */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getResult = { { 3088 /* getResult */, 0, 209, 0, 209, ZEND_ACC_PROTECTED | ZEND_ACC_ABSTRACT } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3139 /* toNativePhpTypeNode */, 1, 210, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3159 /* hasTemplateOrLateResolvableType */, 0, 211, 0, 211, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 3052 /* arithmeticPower */, 2, 205, 2, 207, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 3068 /* getFiniteTypes */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolve = { { 3083 /* resolve */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getResult = { { 3091 /* getResult */, 0, 210, 0, 210, ZEND_ACC_PROTECTED | ZEND_ACC_ABSTRACT } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3142 /* toNativePhpTypeNode */, 1, 211, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3162 /* hasTemplateOrLateResolvableType */, 0, 212, 0, 212, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::LateResolvableTypeTrait diff --git a/turbo-ext/src/generated/MixedType.h b/turbo-ext/src/generated/MixedType.h index b760f482337..8504500c181 100644 --- a/turbo-ext/src/generated/MixedType.h +++ b/turbo-ext/src/generated/MixedType.h @@ -34,6 +34,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); pt_type_trait_substractable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -220,14 +221,14 @@ inline constexpr char strings[] = "typeToRemove\0" /* 3063 */ "tryRemove\0" /* 3076 */ "exponent\0" /* 3086 */ - "exponentiate\0" /* 3095 */ - "getFiniteTypes\0" /* 3108 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3123 */ - "toPhpDocNode\0" /* 3162 */ - "position\0" /* 3175 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3184 */ - "toNativePhpTypeNode\0" /* 3216 */ - "hasTemplateOrLateResolvableType"; /* 3236 */ + "arithmeticPower\0" /* 3095 */ + "getFiniteTypes\0" /* 3111 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3126 */ + "toPhpDocNode\0" /* 3165 */ + "position\0" /* 3178 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3187 */ + "toNativePhpTypeNode\0" /* 3219 */ + "hasTemplateOrLateResolvableType"; /* 3239 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL, reg::NoString, false, false, 16), /* __construct $isExplicitMixed */ reg::packed(22, MAY_BE_NULL, 37, false, false, 55), /* __construct $subtractedType */ @@ -431,11 +432,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(72, 0, 1846), /* looseCompare return */ reg::packed(3063, 0, 37), /* tryRemove $typeToRemove */ reg::packed(72, MAY_BE_NULL, 37), /* tryRemove return */ - reg::packed(3086, 0, 37), /* exponentiate $exponent */ - reg::packed(72, 0, 37), /* exponentiate return */ + reg::packed(3086, 0, 37), /* arithmeticPower $exponent */ + reg::packed(3016, 0, 3027), /* arithmeticPower $phpVersion */ + reg::packed(72, 0, 37), /* arithmeticPower return */ reg::packed(72, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(72, 0, 3123), /* toPhpDocNode return */ - reg::packed(3175, 0, 3184), /* toNativePhpTypeNode $position */ + reg::packed(72, 0, 3126), /* toPhpDocNode return */ + reg::packed(3178, 0, 3187), /* toNativePhpTypeNode $position */ reg::packed(72, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -570,11 +572,11 @@ inline constexpr sigtab::Sig isVoid = { { 3000 /* isVoid */, 0, 195, 0, 195, ZEN inline constexpr sigtab::Sig isScalar = { { 3007 /* isScalar */, 0, 196, 0, 196, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig looseCompare = { { 3050 /* looseCompare */, 2, 197, 2, 199, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 3076 /* tryRemove */, 1, 200, 1, 201, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 3095 /* exponentiate */, 1, 202, 1, 203, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3108 /* getFiniteTypes */, 0, 204, 0, 204, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3162 /* toPhpDocNode */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3216 /* toNativePhpTypeNode */, 1, 206, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3236 /* hasTemplateOrLateResolvableType */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 3095 /* arithmeticPower */, 2, 202, 2, 204, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 3111 /* getFiniteTypes */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3165 /* toPhpDocNode */, 0, 206, 0, 206, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3219 /* toNativePhpTypeNode */, 1, 207, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3239 /* hasTemplateOrLateResolvableType */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::MixedType diff --git a/turbo-ext/src/generated/NeverType.h b/turbo-ext/src/generated/NeverType.h index 5ea65c83d13..7d836fea0a0 100644 --- a/turbo-ext/src/generated/NeverType.h +++ b/turbo-ext/src/generated/NeverType.h @@ -35,6 +35,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -213,14 +214,14 @@ inline constexpr char strings[] = "PHPStan\\Type\\Enum\\EnumCaseObjectType\0" /* 2876 */ "getEnumCaseObject\0" /* 2913 */ "exponent\0" /* 2931 */ - "exponentiate\0" /* 2940 */ - "getFiniteTypes\0" /* 2953 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 2968 */ - "toPhpDocNode\0" /* 3007 */ - "position\0" /* 3020 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3029 */ - "toNativePhpTypeNode\0" /* 3061 */ - "hasTemplateOrLateResolvableType"; /* 3081 */ + "arithmeticPower\0" /* 2940 */ + "getFiniteTypes\0" /* 2956 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 2971 */ + "toPhpDocNode\0" /* 3010 */ + "position\0" /* 3023 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3032 */ + "toNativePhpTypeNode\0" /* 3064 */ + "hasTemplateOrLateResolvableType"; /* 3084 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL, reg::NoString, false, false, 11), /* __construct $isExplicit */ reg::packed(17, MAY_BE_NULL | MAY_BE_STRING, reg::NoString, false, false, 24), /* __construct $reason */ @@ -414,11 +415,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(41, 0, 2825), /* looseCompare return */ reg::packed(41, MAY_BE_ARRAY), /* getEnumCases return */ reg::packed(41, MAY_BE_NULL, 2876), /* getEnumCaseObject return */ - reg::packed(2931, 0, 171), /* exponentiate $exponent */ - reg::packed(41, 0, 171), /* exponentiate return */ + reg::packed(2931, 0, 171), /* arithmeticPower $exponent */ + reg::packed(2791, 0, 2802), /* arithmeticPower $phpVersion */ + reg::packed(41, 0, 171), /* arithmeticPower return */ reg::packed(41, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(41, 0, 2968), /* toPhpDocNode return */ - reg::packed(3020, 0, 3029), /* toNativePhpTypeNode $position */ + reg::packed(41, 0, 2971), /* toPhpDocNode return */ + reg::packed(3023, 0, 3032), /* toNativePhpTypeNode $position */ reg::packed(41, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -547,11 +549,11 @@ inline constexpr sigtab::Sig isScalar = { { 2782 /* isScalar */, 0, 186, 0, 186, inline constexpr sigtab::Sig looseCompare = { { 2850 /* looseCompare */, 2, 187, 2, 189, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getEnumCases = { { 2863 /* getEnumCases */, 0, 190, 0, 190, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getEnumCaseObject = { { 2913 /* getEnumCaseObject */, 0, 191, 0, 191, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 2940 /* exponentiate */, 1, 192, 1, 193, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 2953 /* getFiniteTypes */, 0, 194, 0, 194, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3007 /* toPhpDocNode */, 0, 195, 0, 195, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3061 /* toNativePhpTypeNode */, 1, 196, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3081 /* hasTemplateOrLateResolvableType */, 0, 197, 0, 197, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 2940 /* arithmeticPower */, 2, 192, 2, 194, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 2956 /* getFiniteTypes */, 0, 195, 0, 195, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3010 /* toPhpDocNode */, 0, 196, 0, 196, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3064 /* toNativePhpTypeNode */, 1, 197, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3084 /* hasTemplateOrLateResolvableType */, 0, 198, 0, 198, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NeverType diff --git a/turbo-ext/src/generated/NewObjectType.h b/turbo-ext/src/generated/NewObjectType.h index fba4b97f9c7..0c176c0439e 100644 --- a/turbo-ext/src/generated/NewObjectType.h +++ b/turbo-ext/src/generated/NewObjectType.h @@ -29,6 +29,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/NonEmptyArrayType.h b/turbo-ext/src/generated/NonEmptyArrayType.h index 543ecac4dc1..c1386a547b4 100644 --- a/turbo-ext/src/generated/NonEmptyArrayType.h +++ b/turbo-ext/src/generated/NonEmptyArrayType.h @@ -30,6 +30,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -155,15 +156,15 @@ inline constexpr char strings[] = "right\0" /* 1744 */ "traverseSimultaneously\0" /* 1750 */ "exponent\0" /* 1773 */ - "exponentiate\0" /* 1782 */ - "getFiniteTypes\0" /* 1795 */ - "getDefaultBaseType\0" /* 1810 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1829 */ - "toPhpDocNode\0" /* 1868 */ - "position\0" /* 1881 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1890 */ - "toNativePhpTypeNode\0" /* 1922 */ - "hasTemplateOrLateResolvableType"; /* 1942 */ + "arithmeticPower\0" /* 1782 */ + "getFiniteTypes\0" /* 1798 */ + "getDefaultBaseType\0" /* 1813 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1832 */ + "toPhpDocNode\0" /* 1871 */ + "position\0" /* 1884 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1893 */ + "toNativePhpTypeNode\0" /* 1925 */ + "hasTemplateOrLateResolvableType"; /* 1945 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -294,12 +295,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1744, 0, 132), /* traverseSimultaneously $right */ reg::packed(857, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(12, 0, 132), /* traverseSimultaneously return */ - reg::packed(1773, 0, 132), /* exponentiate $exponent */ - reg::packed(12, 0, 132), /* exponentiate return */ + reg::packed(1773, 0, 132), /* arithmeticPower $exponent */ + reg::packed(1552, 0, 1563), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 132), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 132), /* getDefaultBaseType return */ - reg::packed(12, 0, 1829), /* toPhpDocNode return */ - reg::packed(1881, 0, 1890), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1832), /* toPhpDocNode return */ + reg::packed(1884, 0, 1893), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -396,12 +398,12 @@ inline constexpr sigtab::Sig toArrayKey = { { 1702 /* toArrayKey */, 0, 121, 0, inline constexpr sigtab::Sig toCoercedArgumentType = { { 1713 /* toCoercedArgumentType */, 1, 122, 1, 123, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1735 /* traverse */, 1, 124, 1, 125, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1750 /* traverseSimultaneously */, 2, 126, 2, 128, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1782 /* exponentiate */, 1, 129, 1, 130, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1795 /* getFiniteTypes */, 0, 131, 0, 131, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1810 /* getDefaultBaseType */, 0, 132, 0, 132, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1868 /* toPhpDocNode */, 0, 133, 0, 133, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1922 /* toNativePhpTypeNode */, 1, 134, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1942 /* hasTemplateOrLateResolvableType */, 0, 135, 0, 135, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1782 /* arithmeticPower */, 2, 129, 2, 131, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1798 /* getFiniteTypes */, 0, 132, 0, 132, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1813 /* getDefaultBaseType */, 0, 133, 0, 133, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1871 /* toPhpDocNode */, 0, 134, 0, 134, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1925 /* toNativePhpTypeNode */, 1, 135, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1945 /* hasTemplateOrLateResolvableType */, 0, 136, 0, 136, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NonEmptyArrayType diff --git a/turbo-ext/src/generated/NonexistentParentClassType.h b/turbo-ext/src/generated/NonexistentParentClassType.h index ce54c99d5b4..63c2608ef12 100644 --- a/turbo-ext/src/generated/NonexistentParentClassType.h +++ b/turbo-ext/src/generated/NonexistentParentClassType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -109,14 +110,14 @@ inline constexpr char strings[] = "PHPStan\\Type\\Enum\\EnumCaseObjectType\0" /* 1358 */ "getEnumCaseObject\0" /* 1395 */ "exponent\0" /* 1413 */ - "exponentiate\0" /* 1422 */ - "getFiniteTypes\0" /* 1435 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1450 */ - "toPhpDocNode\0" /* 1489 */ - "position\0" /* 1502 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1511 */ - "toNativePhpTypeNode\0" /* 1543 */ - "hasTemplateOrLateResolvableType"; /* 1563 */ + "arithmeticPower\0" /* 1422 */ + "getFiniteTypes\0" /* 1438 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1453 */ + "toPhpDocNode\0" /* 1492 */ + "position\0" /* 1505 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1514 */ + "toNativePhpTypeNode\0" /* 1546 */ + "hasTemplateOrLateResolvableType"; /* 1566 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 6), /* describe $level */ reg::packed(34, MAY_BE_STRING), /* describe return */ @@ -192,11 +193,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(34, 0, 1307), /* looseCompare return */ reg::packed(34, MAY_BE_ARRAY), /* getEnumCases return */ reg::packed(34, MAY_BE_NULL, 1358), /* getEnumCaseObject return */ - reg::packed(1413, 0, 79), /* exponentiate $exponent */ - reg::packed(34, 0, 79), /* exponentiate return */ + reg::packed(1413, 0, 79), /* arithmeticPower $exponent */ + reg::packed(1273, 0, 1284), /* arithmeticPower $phpVersion */ + reg::packed(34, 0, 79), /* arithmeticPower return */ reg::packed(34, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(34, 0, 1450), /* toPhpDocNode return */ - reg::packed(1502, 0, 1511), /* toNativePhpTypeNode $position */ + reg::packed(34, 0, 1453), /* toPhpDocNode return */ + reg::packed(1505, 0, 1514), /* toNativePhpTypeNode $position */ reg::packed(34, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -246,11 +248,11 @@ inline constexpr sigtab::Sig isScalar = { { 1259 /* isScalar */, 0, 68, 0, 68, Z inline constexpr sigtab::Sig looseCompare = { { 1332 /* looseCompare */, 2, 69, 2, 71, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getEnumCases = { { 1345 /* getEnumCases */, 0, 72, 0, 72, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getEnumCaseObject = { { 1395 /* getEnumCaseObject */, 0, 73, 0, 73, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1422 /* exponentiate */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1435 /* getFiniteTypes */, 0, 76, 0, 76, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1489 /* toPhpDocNode */, 0, 77, 0, 77, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1543 /* toNativePhpTypeNode */, 1, 78, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1563 /* hasTemplateOrLateResolvableType */, 0, 79, 0, 79, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1422 /* arithmeticPower */, 2, 74, 2, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1438 /* getFiniteTypes */, 0, 77, 0, 77, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1492 /* toPhpDocNode */, 0, 78, 0, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1546 /* toNativePhpTypeNode */, 1, 79, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1566 /* hasTemplateOrLateResolvableType */, 0, 80, 0, 80, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NonexistentParentClassType diff --git a/turbo-ext/src/generated/NullType.h b/turbo-ext/src/generated/NullType.h index 90b0b540b9e..cd636fa80ec 100644 --- a/turbo-ext/src/generated/NullType.h +++ b/turbo-ext/src/generated/NullType.h @@ -29,6 +29,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_falsey_boolean(cls); pt_type_trait_non_generic(cls); pt_type_trait_non_removeable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -119,13 +120,13 @@ inline constexpr char strings[] = "getGreaterOrEqualType\0" /* 1282 */ "getFiniteTypes\0" /* 1304 */ "exponent\0" /* 1319 */ - "exponentiate\0" /* 1328 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1341 */ - "toPhpDocNode\0" /* 1380 */ - "position\0" /* 1393 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1402 */ - "toNativePhpTypeNode\0" /* 1434 */ - "hasTemplateOrLateResolvableType"; /* 1454 */ + "arithmeticPower\0" /* 1328 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1344 */ + "toPhpDocNode\0" /* 1383 */ + "position\0" /* 1396 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1405 */ + "toNativePhpTypeNode\0" /* 1437 */ + "hasTemplateOrLateResolvableType"; /* 1457 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -215,10 +216,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(296, 0, 307), /* getGreaterOrEqualType $phpVersion */ reg::packed(12, 0, 151), /* getGreaterOrEqualType return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(1319, 0, 151), /* exponentiate $exponent */ - reg::packed(12, 0, 151), /* exponentiate return */ - reg::packed(12, 0, 1341), /* toPhpDocNode return */ - reg::packed(1393, 0, 1402), /* toNativePhpTypeNode $position */ + reg::packed(1319, 0, 151), /* arithmeticPower $exponent */ + reg::packed(296, 0, 307), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 151), /* arithmeticPower return */ + reg::packed(12, 0, 1344), /* toPhpDocNode return */ + reg::packed(1396, 0, 1405), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -287,10 +289,10 @@ inline constexpr sigtab::Sig getSmallerOrEqualType = { { 1245 /* getSmallerOrEqu inline constexpr sigtab::Sig getGreaterType = { { 1267 /* getGreaterType */, 1, 83, 1, 84, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getGreaterOrEqualType = { { 1282 /* getGreaterOrEqualType */, 1, 85, 1, 86, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFiniteTypes = { { 1304 /* getFiniteTypes */, 0, 87, 0, 87, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1328 /* exponentiate */, 1, 88, 1, 89, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1380 /* toPhpDocNode */, 0, 90, 0, 90, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1434 /* toNativePhpTypeNode */, 1, 91, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1454 /* hasTemplateOrLateResolvableType */, 0, 92, 0, 92, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1328 /* arithmeticPower */, 2, 88, 2, 90, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1383 /* toPhpDocNode */, 0, 91, 0, 91, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1437 /* toNativePhpTypeNode */, 1, 92, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1457 /* hasTemplateOrLateResolvableType */, 0, 93, 0, 93, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NullType diff --git a/turbo-ext/src/generated/ObjectShapeType.h b/turbo-ext/src/generated/ObjectShapeType.h index 4d87d106d6f..a1e2eb57c6b 100644 --- a/turbo-ext/src/generated/ObjectShapeType.h +++ b/turbo-ext/src/generated/ObjectShapeType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_object(cls); pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -91,14 +92,16 @@ inline constexpr char strings[] = "right\0" /* 1040 */ "traverseSimultaneously\0" /* 1046 */ "exponent\0" /* 1069 */ - "exponentiate\0" /* 1078 */ - "getFiniteTypes\0" /* 1091 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1106 */ - "toPhpDocNode\0" /* 1145 */ - "position\0" /* 1158 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1167 */ - "toNativePhpTypeNode\0" /* 1199 */ - "hasTemplateOrLateResolvableType"; /* 1219 */ + "phpVersion\0" /* 1078 */ + "PHPStan\\Php\\PhpVersion\0" /* 1089 */ + "arithmeticPower\0" /* 1112 */ + "getFiniteTypes\0" /* 1128 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1143 */ + "toPhpDocNode\0" /* 1182 */ + "position\0" /* 1195 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1204 */ + "toNativePhpTypeNode\0" /* 1236 */ + "hasTemplateOrLateResolvableType"; /* 1256 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $properties */ reg::packed(11, MAY_BE_ARRAY), /* __construct $optionalProperties */ @@ -156,11 +159,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1040, 0, 146), /* traverseSimultaneously $right */ reg::packed(1028, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(42, 0, 146), /* traverseSimultaneously return */ - reg::packed(1069, 0, 146), /* exponentiate $exponent */ - reg::packed(42, 0, 146), /* exponentiate return */ + reg::packed(1069, 0, 146), /* arithmeticPower $exponent */ + reg::packed(1078, 0, 1089), /* arithmeticPower $phpVersion */ + reg::packed(42, 0, 146), /* arithmeticPower return */ reg::packed(42, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(42, 0, 1106), /* toPhpDocNode return */ - reg::packed(1158, 0, 1167), /* toNativePhpTypeNode $position */ + reg::packed(42, 0, 1143), /* toPhpDocNode return */ + reg::packed(1195, 0, 1204), /* toNativePhpTypeNode $position */ reg::packed(42, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -196,11 +200,11 @@ inline constexpr sigtab::Sig getEnumCases = { { 960 /* getEnumCases */, 0, 49, 0 inline constexpr sigtab::Sig getEnumCaseObject = { { 1010 /* getEnumCaseObject */, 0, 50, 0, 50, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1031 /* traverse */, 1, 51, 1, 52, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1046 /* traverseSimultaneously */, 2, 53, 2, 55, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1078 /* exponentiate */, 1, 56, 1, 57, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1091 /* getFiniteTypes */, 0, 58, 0, 58, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1145 /* toPhpDocNode */, 0, 59, 0, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1199 /* toNativePhpTypeNode */, 1, 60, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1219 /* hasTemplateOrLateResolvableType */, 0, 61, 0, 61, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1112 /* arithmeticPower */, 2, 56, 2, 58, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1128 /* getFiniteTypes */, 0, 59, 0, 59, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1182 /* toPhpDocNode */, 0, 60, 0, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1236 /* toNativePhpTypeNode */, 1, 61, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1256 /* hasTemplateOrLateResolvableType */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ObjectShapeType diff --git a/turbo-ext/src/generated/ObjectType.h b/turbo-ext/src/generated/ObjectType.h index 1e8f8ce5980..be2ec69571d 100644 --- a/turbo-ext/src/generated/ObjectType.h +++ b/turbo-ext/src/generated/ObjectType.h @@ -60,6 +60,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); pt_type_trait_substractable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -227,13 +228,13 @@ inline constexpr char strings[] = "tryRemove\0" /* 2955 */ "getFiniteTypes\0" /* 2965 */ "exponent\0" /* 2980 */ - "exponentiate\0" /* 2989 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3002 */ - "toPhpDocNode\0" /* 3041 */ - "position\0" /* 3054 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3063 */ - "toNativePhpTypeNode\0" /* 3095 */ - "hasTemplateOrLateResolvableType"; /* 3115 */ + "arithmeticPower\0" /* 2989 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3005 */ + "toPhpDocNode\0" /* 3044 */ + "position\0" /* 3057 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3066 */ + "toNativePhpTypeNode\0" /* 3098 */ + "hasTemplateOrLateResolvableType"; /* 3118 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_STRING), /* __construct $className */ reg::packed(10, MAY_BE_NULL, 25, false, false, 43), /* __construct $subtractedType */ @@ -407,10 +408,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(2942, 0, 25), /* tryRemove $typeToRemove */ reg::packed(111, MAY_BE_NULL, 25), /* tryRemove return */ reg::packed(111, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(2980, 0, 25), /* exponentiate $exponent */ - reg::packed(111, 0, 25), /* exponentiate return */ - reg::packed(111, 0, 3002), /* toPhpDocNode return */ - reg::packed(3054, 0, 3063), /* toNativePhpTypeNode $position */ + reg::packed(2980, 0, 25), /* arithmeticPower $exponent */ + reg::packed(2201, 0, 2212), /* arithmeticPower $phpVersion */ + reg::packed(111, 0, 25), /* arithmeticPower return */ + reg::packed(111, 0, 3005), /* toPhpDocNode return */ + reg::packed(3057, 0, 3066), /* toNativePhpTypeNode $position */ reg::packed(111, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -532,10 +534,10 @@ inline constexpr sigtab::Sig getParent = { { 2918 /* getParent */, 0, 167, 0, 16 inline constexpr sigtab::Sig getInterfaces = { { 2928 /* getInterfaces */, 0, 168, 0, 168, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig tryRemove = { { 2955 /* tryRemove */, 1, 169, 1, 170, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFiniteTypes = { { 2965 /* getFiniteTypes */, 0, 171, 0, 171, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 2989 /* exponentiate */, 1, 172, 1, 173, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3041 /* toPhpDocNode */, 0, 174, 0, 174, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3095 /* toNativePhpTypeNode */, 1, 175, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3115 /* hasTemplateOrLateResolvableType */, 0, 176, 0, 176, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 2989 /* arithmeticPower */, 2, 172, 2, 174, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3044 /* toPhpDocNode */, 0, 175, 0, 175, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3098 /* toNativePhpTypeNode */, 1, 176, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3118 /* hasTemplateOrLateResolvableType */, 0, 177, 0, 177, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ObjectType diff --git a/turbo-ext/src/generated/ObjectWithoutClassType.h b/turbo-ext/src/generated/ObjectWithoutClassType.h index ccfc39a9f11..9f9d61804ab 100644 --- a/turbo-ext/src/generated/ObjectWithoutClassType.h +++ b/turbo-ext/src/generated/ObjectWithoutClassType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); pt_type_trait_substractable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -70,14 +71,16 @@ inline constexpr char strings[] = "typeToRemove\0" /* 472 */ "tryRemove\0" /* 485 */ "exponent\0" /* 495 */ - "exponentiate\0" /* 504 */ - "getFiniteTypes\0" /* 517 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 532 */ - "toPhpDocNode\0" /* 571 */ - "position\0" /* 584 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 593 */ - "toNativePhpTypeNode\0" /* 625 */ - "hasTemplateOrLateResolvableType"; /* 645 */ + "phpVersion\0" /* 504 */ + "PHPStan\\Php\\PhpVersion\0" /* 515 */ + "arithmeticPower\0" /* 538 */ + "getFiniteTypes\0" /* 554 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 569 */ + "toPhpDocNode\0" /* 608 */ + "position\0" /* 621 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 630 */ + "toNativePhpTypeNode\0" /* 662 */ + "hasTemplateOrLateResolvableType"; /* 682 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_NULL, 15, false, false, 33), /* __construct $subtractedType */ reg::packed(50, MAY_BE_ARRAY), /* getReferencedClasses return */ @@ -108,11 +111,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(50, 0, 15), /* traverseSimultaneously return */ reg::packed(472, 0, 15), /* tryRemove $typeToRemove */ reg::packed(50, MAY_BE_NULL, 15), /* tryRemove return */ - reg::packed(495, 0, 15), /* exponentiate $exponent */ - reg::packed(50, 0, 15), /* exponentiate return */ + reg::packed(495, 0, 15), /* arithmeticPower $exponent */ + reg::packed(504, 0, 515), /* arithmeticPower $phpVersion */ + reg::packed(50, 0, 15), /* arithmeticPower return */ reg::packed(50, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(50, 0, 532), /* toPhpDocNode return */ - reg::packed(584, 0, 593), /* toNativePhpTypeNode $position */ + reg::packed(50, 0, 569), /* toPhpDocNode return */ + reg::packed(621, 0, 630), /* toNativePhpTypeNode $position */ reg::packed(50, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -138,11 +142,11 @@ inline constexpr sigtab::Sig getSubtractedType = { { 413 /* getSubtractedType */ inline constexpr sigtab::Sig traverse = { { 434 /* traverse */, 1, 22, 1, 23, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 449 /* traverseSimultaneously */, 2, 24, 2, 26, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 485 /* tryRemove */, 1, 27, 1, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 504 /* exponentiate */, 1, 29, 1, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 517 /* getFiniteTypes */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 571 /* toPhpDocNode */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 625 /* toNativePhpTypeNode */, 1, 33, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 645 /* hasTemplateOrLateResolvableType */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 538 /* arithmeticPower */, 2, 29, 2, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 554 /* getFiniteTypes */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 608 /* toPhpDocNode */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 662 /* toNativePhpTypeNode */, 1, 34, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 682 /* hasTemplateOrLateResolvableType */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ObjectWithoutClassType diff --git a/turbo-ext/src/generated/OffsetAccessType.h b/turbo-ext/src/generated/OffsetAccessType.h index 6fe20b482b9..a61731d4110 100644 --- a/turbo-ext/src/generated/OffsetAccessType.h +++ b/turbo-ext/src/generated/OffsetAccessType.h @@ -32,6 +32,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h new file mode 100644 index 00000000000..e7ed79e5603 --- /dev/null +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -0,0 +1,241 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Type/OperatorTypeHelper.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_OPERATOR_TYPE_HELPER_H +#define PHPSTANTURBO_GENERATED_OPERATOR_TYPE_HELPER_H + +#include "../reg.h" + +namespace ptdecl::OperatorTypeHelper { + +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) +{ + (void) cls; +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "left\0" /* 0 */ + "PHPStan\\Type\\Type\0" /* 5 */ + "right\0" /* 23 */ + "phpVersion\0" /* 29 */ + "PHPStan\\Php\\PhpVersion\0" /* 40 */ + "\0" /* 63 */ + "concat\0" /* 64 */ + "leftType\0" /* 71 */ + "rightType\0" /* 80 */ + "bitwiseAnd\0" /* 90 */ + "bitwiseOr\0" /* 101 */ + "bitwiseXor\0" /* 111 */ + "operationCallable\0" /* 122 */ + "getFiniteOrConstantScalarTypes\0" /* 140 */ + "divide\0" /* 171 */ + "getDivTypeFromTypes\0" /* 178 */ + "modulo\0" /* 198 */ + "divisorMin\0" /* 205 */ + "divisorMax\0" /* 216 */ + "getMaxModuloMagnitude\0" /* 227 */ + "type\0" /* 249 */ + "getIntegerBounds\0" /* 254 */ + "add\0" /* 271 */ + "arrayUnion\0" /* 275 */ + "tryArrayUnion\0" /* 286 */ + "subtract\0" /* 300 */ + "multiply\0" /* 309 */ + "shiftLeft\0" /* 318 */ + "shiftRight\0" /* 328 */ + "optimizeScalarType\0" /* 339 */ + "getNonNegativeIntegerBounds\0" /* 358 */ + "leftNumberType\0" /* 386 */ + "rightNumberType\0" /* 401 */ + "computeBitwiseAndRange\0" /* 417 */ + "computeBitwiseOrXorRange\0" /* 440 */ + "value\0" /* 465 */ + "allBitsMask\0" /* 471 */ + "operator\0" /* 483 */ + "resolveCommonMath\0" /* 492 */ + "range\0" /* 510 */ + "operand\0" /* 516 */ + "integerRangeMath\0" /* 524 */ + "shift\0" /* 541 */ + "shiftLeftOverflows\0" /* 547 */ + "negateNumber\0" /* 566 */ + "toIntBound\0" /* 579 */ + "getTypeFromValue\0" /* 590 */ + "getNeverType\0" /* 607 */ + "negate\0" /* 620 */ + "numeratorType\0" /* 627 */ + "divisorType\0" /* 641 */ + "intDivide\0" /* 653 */ + "getIntervals\0" /* 663 */ + "numeratorMin\0" /* 676 */ + "numeratorMax\0" /* 689 */ + "divideIntervals\0" /* 702 */ + "numerator\0" /* 718 */ + "divisor\0" /* 728 */ + "divideBounds"; /* 736 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, 0, 5), /* concat $left */ + reg::packed(23, 0, 5), /* concat $right */ + reg::packed(29, 0, 40), /* concat $phpVersion */ + reg::packed(63, 0, 5), /* concat return */ + reg::packed(71, 0, 5), /* bitwiseAnd $leftType */ + reg::packed(80, 0, 5), /* bitwiseAnd $rightType */ + reg::packed(29, 0, 40), /* bitwiseAnd $phpVersion */ + reg::packed(63, 0, 5), /* bitwiseAnd return */ + reg::packed(71, 0, 5), /* bitwiseOr $leftType */ + reg::packed(80, 0, 5), /* bitwiseOr $rightType */ + reg::packed(29, 0, 40), /* bitwiseOr $phpVersion */ + reg::packed(63, 0, 5), /* bitwiseOr return */ + reg::packed(71, 0, 5), /* bitwiseXor $leftType */ + reg::packed(80, 0, 5), /* bitwiseXor $rightType */ + reg::packed(29, 0, 40), /* bitwiseXor $phpVersion */ + reg::packed(63, 0, 5), /* bitwiseXor return */ + reg::packed(71, 0, 5), /* getFiniteOrConstantScalarTypes $leftType */ + reg::packed(80, 0, 5), /* getFiniteOrConstantScalarTypes $rightType */ + reg::packed(122, MAY_BE_CALLABLE), /* getFiniteOrConstantScalarTypes $operationCallable */ + reg::packed(63, MAY_BE_LONG, 5), /* getFiniteOrConstantScalarTypes return */ + reg::packed(71, 0, 5), /* divide $leftType */ + reg::packed(80, 0, 5), /* divide $rightType */ + reg::packed(29, 0, 40), /* divide $phpVersion */ + reg::packed(63, 0, 5), /* divide return */ + reg::packed(71, 0, 5), /* getDivTypeFromTypes $leftType */ + reg::packed(80, 0, 5), /* getDivTypeFromTypes $rightType */ + reg::packed(63, 0, 5), /* getDivTypeFromTypes return */ + reg::packed(71, 0, 5), /* modulo $leftType */ + reg::packed(80, 0, 5), /* modulo $rightType */ + reg::packed(29, 0, 40), /* modulo $phpVersion */ + reg::packed(63, 0, 5), /* modulo return */ + reg::packed(205, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ + reg::packed(216, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ + reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude return */ + reg::packed(249, 0, 5), /* getIntegerBounds $type */ + reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerBounds return */ + reg::packed(71, 0, 5), /* add $leftType */ + reg::packed(80, 0, 5), /* add $rightType */ + reg::packed(29, 0, 40), /* add $phpVersion */ + reg::packed(63, 0, 5), /* add return */ + reg::packed(71, 0, 5), /* arrayUnion $leftType */ + reg::packed(80, 0, 5), /* arrayUnion $rightType */ + reg::packed(29, 0, 40), /* arrayUnion $phpVersion */ + reg::packed(63, 0, 5), /* arrayUnion return */ + reg::packed(71, 0, 5), /* tryArrayUnion $leftType */ + reg::packed(80, 0, 5), /* tryArrayUnion $rightType */ + reg::packed(63, MAY_BE_NULL, 5), /* tryArrayUnion return */ + reg::packed(71, 0, 5), /* subtract $leftType */ + reg::packed(80, 0, 5), /* subtract $rightType */ + reg::packed(29, 0, 40), /* subtract $phpVersion */ + reg::packed(63, 0, 5), /* subtract return */ + reg::packed(71, 0, 5), /* multiply $leftType */ + reg::packed(80, 0, 5), /* multiply $rightType */ + reg::packed(29, 0, 40), /* multiply $phpVersion */ + reg::packed(63, 0, 5), /* multiply return */ + reg::packed(71, 0, 5), /* shiftLeft $leftType */ + reg::packed(80, 0, 5), /* shiftLeft $rightType */ + reg::packed(29, 0, 40), /* shiftLeft $phpVersion */ + reg::packed(63, 0, 5), /* shiftLeft return */ + reg::packed(71, 0, 5), /* shiftRight $leftType */ + reg::packed(80, 0, 5), /* shiftRight $rightType */ + reg::packed(29, 0, 40), /* shiftRight $phpVersion */ + reg::packed(63, 0, 5), /* shiftRight return */ + reg::packed(249, 0, 5), /* optimizeScalarType $type */ + reg::packed(63, 0, 5), /* optimizeScalarType return */ + reg::packed(249, 0, 5), /* getNonNegativeIntegerBounds $type */ + reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ + reg::packed(386, 0, 5), /* computeBitwiseAndRange $leftNumberType */ + reg::packed(401, 0, 5), /* computeBitwiseAndRange $rightNumberType */ + reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseAndRange return */ + reg::packed(386, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ + reg::packed(401, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ + reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseOrXorRange return */ + reg::packed(465, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(63, MAY_BE_LONG), /* allBitsMask return */ + reg::packed(483, MAY_BE_STRING), /* resolveCommonMath $operator */ + reg::packed(71, 0, 5), /* resolveCommonMath $leftType */ + reg::packed(80, 0, 5), /* resolveCommonMath $rightType */ + reg::packed(63, 0, 5), /* resolveCommonMath return */ + reg::packed(510, 0, 5), /* integerRangeMath $range */ + reg::packed(483, MAY_BE_STRING), /* integerRangeMath $operator */ + reg::packed(516, 0, 5), /* integerRangeMath $operand */ + reg::packed(63, 0, 5), /* integerRangeMath return */ + reg::packed(465, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(541, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(63, MAY_BE_BOOL), /* shiftLeftOverflows return */ + reg::packed(249, 0, 5), /* negateNumber $type */ + reg::packed(63, 0, 5), /* negateNumber return */ + reg::packed(465, MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ + reg::packed(465, 0), /* getTypeFromValue $value */ + reg::packed(63, 0, 5), /* getTypeFromValue return */ + reg::packed(71, 0, 5), /* getNeverType $leftType */ + reg::packed(80, 0, 5), /* getNeverType $rightType */ + reg::packed(63, 0, 5), /* getNeverType return */ + reg::packed(249, 0, 5), /* negate $type */ + reg::packed(29, 0, 40), /* negate $phpVersion */ + reg::packed(63, 0, 5), /* negate return */ + reg::packed(627, 0, 5), /* intDivide $numeratorType */ + reg::packed(641, 0, 5), /* intDivide $divisorType */ + reg::packed(29, 0, 40), /* intDivide $phpVersion */ + reg::packed(63, 0, 5), /* intDivide return */ + reg::packed(249, 0, 5), /* getIntervals $type */ + reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntervals return */ + reg::packed(676, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ + reg::packed(689, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ + reg::packed(205, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ + reg::packed(216, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ + reg::packed(63, 0, 5), /* divideIntervals return */ + reg::packed(718, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ + reg::packed(728, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ + reg::packed(63, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds 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 concat = { { 64 /* concat */, 3, 0, 3, 3, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 90 /* bitwiseAnd */, 3, 4, 3, 7, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 101 /* bitwiseOr */, 3, 8, 3, 11, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 111 /* bitwiseXor */, 3, 12, 3, 15, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFiniteOrConstantScalarTypes = { { 140 /* getFiniteOrConstantScalarTypes */, 3, 16, 3, 19, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divide = { { 171 /* divide */, 3, 20, 3, 23, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getDivTypeFromTypes = { { 178 /* getDivTypeFromTypes */, 2, 24, 2, 26, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig modulo = { { 198 /* modulo */, 3, 27, 3, 30, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 227 /* getMaxModuloMagnitude */, 2, 31, 2, 33, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerBounds = { { 254 /* getIntegerBounds */, 1, 34, 1, 35, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig add = { { 271 /* add */, 3, 36, 3, 39, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 275 /* arrayUnion */, 3, 40, 3, 43, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 286 /* tryArrayUnion */, 2, 44, 2, 46, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig subtract = { { 300 /* subtract */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig multiply = { { 309 /* multiply */, 3, 51, 3, 54, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 318 /* shiftLeft */, 3, 55, 3, 58, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftRight = { { 328 /* shiftRight */, 3, 59, 3, 62, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig optimizeScalarType = { { 339 /* optimizeScalarType */, 1, 63, 1, 64, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 358 /* getNonNegativeIntegerBounds */, 1, 65, 1, 66, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseAndRange = { { 417 /* computeBitwiseAndRange */, 2, 67, 2, 69, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 440 /* computeBitwiseOrXorRange */, 2, 70, 2, 72, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig allBitsMask = { { 471 /* allBitsMask */, 1, 73, 1, 74, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolveCommonMath = { { 492 /* resolveCommonMath */, 3, 75, 3, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig integerRangeMath = { { 524 /* integerRangeMath */, 3, 79, 3, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 547 /* shiftLeftOverflows */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negateNumber = { { 566 /* negateNumber */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 579 /* toIntBound */, 1, 88, 1, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 590 /* getTypeFromValue */, 1, 90, 1, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNeverType = { { 607 /* getNeverType */, 2, 92, 2, 94, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negate = { { 620 /* negate */, 2, 95, 2, 97, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 653 /* intDivide */, 3, 98, 3, 101, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntervals = { { 663 /* getIntervals */, 1, 102, 1, 103, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntervals = { { 702 /* divideIntervals */, 4, 104, 4, 108, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBounds = { { 736 /* divideBounds */, 2, 109, 2, 111, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +} // namespace sig + +} // namespace ptdecl::OperatorTypeHelper + +#endif diff --git a/turbo-ext/src/generated/OperatorTypeTrait.h b/turbo-ext/src/generated/OperatorTypeTrait.h new file mode 100644 index 00000000000..d37cdd269bd --- /dev/null +++ b/turbo-ext/src/generated/OperatorTypeTrait.h @@ -0,0 +1,104 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Type/Traits/OperatorTypeTrait.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_OPERATOR_TYPE_TRAIT_H +#define PHPSTANTURBO_GENERATED_OPERATOR_TYPE_TRAIT_H + +#include "../reg.h" + +namespace ptdecl::OperatorTypeTrait { + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "exponent\0" /* 0 */ + "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 */ + "stringConcatenate\0" /* 271 */ + "arrayUnion"; /* 289 */ +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, 0, 9), /* arithmeticAdd return */ + reg::packed(41, 0, 9), /* arithmeticSubtract $right */ + reg::packed(47, 0, 58), /* 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(27, 0, 9), /* arithmeticMultiply return */ + reg::packed(41, 0, 9), /* arithmeticDivide $right */ + reg::packed(47, 0, 58), /* 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(27, 0, 9), /* arithmeticIntDivide return */ + reg::packed(41, 0, 9), /* arithmeticModulo $right */ + reg::packed(47, 0, 58), /* arithmeticModulo $phpVersion */ + reg::packed(27, 0, 9), /* arithmeticModulo return */ + reg::packed(47, 0, 58), /* 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(27, 0, 9), /* bitwiseAnd return */ + reg::packed(41, 0, 9), /* bitwiseOr $right */ + reg::packed(47, 0, 58), /* 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(27, 0, 9), /* bitwiseXor return */ + reg::packed(41, 0, 9), /* bitwiseShiftLeft $right */ + reg::packed(47, 0, 58), /* 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(27, 0, 9), /* bitwiseShiftRight return */ + reg::packed(41, 0, 9), /* stringConcatenate $right */ + reg::packed(47, 0, 58), /* 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(27, 0, 9), /* arrayUnion return */ +}; +using Sig = reg::Sig; +} // namespace sigtab + +/* 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 stringConcatenate = { { 271 /* stringConcatenate */, 2, 37, 2, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 289 /* arrayUnion */, 2, 40, 2, 42, ZEND_ACC_PUBLIC } }; +} // namespace sig + +} // namespace ptdecl::OperatorTypeTrait + +#endif diff --git a/turbo-ext/src/generated/OversizedArrayType.h b/turbo-ext/src/generated/OversizedArrayType.h index 63d4d58659e..9007b4eb3a4 100644 --- a/turbo-ext/src/generated/OversizedArrayType.h +++ b/turbo-ext/src/generated/OversizedArrayType.h @@ -29,6 +29,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -155,15 +156,15 @@ inline constexpr char strings[] = "right\0" /* 1754 */ "traverseSimultaneously\0" /* 1760 */ "exponent\0" /* 1783 */ - "exponentiate\0" /* 1792 */ - "getFiniteTypes\0" /* 1805 */ - "getDefaultBaseType\0" /* 1820 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1839 */ - "toPhpDocNode\0" /* 1878 */ - "position\0" /* 1891 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 1900 */ - "toNativePhpTypeNode\0" /* 1932 */ - "hasTemplateOrLateResolvableType"; /* 1952 */ + "arithmeticPower\0" /* 1792 */ + "getFiniteTypes\0" /* 1808 */ + "getDefaultBaseType\0" /* 1823 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1842 */ + "toPhpDocNode\0" /* 1881 */ + "position\0" /* 1894 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 1903 */ + "toNativePhpTypeNode\0" /* 1935 */ + "hasTemplateOrLateResolvableType"; /* 1955 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -295,12 +296,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1754, 0, 132), /* traverseSimultaneously $right */ reg::packed(857, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(12, 0, 132), /* traverseSimultaneously return */ - reg::packed(1783, 0, 132), /* exponentiate $exponent */ - reg::packed(12, 0, 132), /* exponentiate return */ + reg::packed(1783, 0, 132), /* arithmeticPower $exponent */ + reg::packed(1552, 0, 1563), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 132), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, 0, 132), /* getDefaultBaseType return */ - reg::packed(12, 0, 1839), /* toPhpDocNode return */ - reg::packed(1891, 0, 1900), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 1842), /* toPhpDocNode return */ + reg::packed(1894, 0, 1903), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -398,12 +400,12 @@ inline constexpr sigtab::Sig toArrayKey = { { 1712 /* toArrayKey */, 0, 122, 0, inline constexpr sigtab::Sig toCoercedArgumentType = { { 1723 /* toCoercedArgumentType */, 1, 123, 1, 124, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 1745 /* traverse */, 1, 125, 1, 126, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 1760 /* traverseSimultaneously */, 2, 127, 2, 129, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 1792 /* exponentiate */, 1, 130, 1, 131, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 1805 /* getFiniteTypes */, 0, 132, 0, 132, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDefaultBaseType = { { 1820 /* getDefaultBaseType */, 0, 133, 0, 133, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 1878 /* toPhpDocNode */, 0, 134, 0, 134, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1932 /* toNativePhpTypeNode */, 1, 135, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1952 /* hasTemplateOrLateResolvableType */, 0, 136, 0, 136, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 1792 /* arithmeticPower */, 2, 130, 2, 132, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 1808 /* getFiniteTypes */, 0, 133, 0, 133, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDefaultBaseType = { { 1823 /* getDefaultBaseType */, 0, 134, 0, 134, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 1881 /* toPhpDocNode */, 0, 135, 0, 135, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1935 /* toNativePhpTypeNode */, 1, 136, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1955 /* hasTemplateOrLateResolvableType */, 0, 137, 0, 137, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::OversizedArrayType diff --git a/turbo-ext/src/generated/ResourceType.h b/turbo-ext/src/generated/ResourceType.h index 916331eb7d9..808d3d99de5 100644 --- a/turbo-ext/src/generated/ResourceType.h +++ b/turbo-ext/src/generated/ResourceType.h @@ -33,6 +33,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_offset_accessible(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -64,14 +65,14 @@ inline constexpr char strings[] = "PHPStan\\Type\\BooleanType\0" /* 305 */ "looseCompare\0" /* 330 */ "exponent\0" /* 343 */ - "exponentiate\0" /* 352 */ - "getFiniteTypes\0" /* 365 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 380 */ - "toPhpDocNode\0" /* 419 */ - "position\0" /* 432 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 441 */ - "toNativePhpTypeNode\0" /* 473 */ - "hasTemplateOrLateResolvableType"; /* 493 */ + "arithmeticPower\0" /* 352 */ + "getFiniteTypes\0" /* 368 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 383 */ + "toPhpDocNode\0" /* 422 */ + "position\0" /* 435 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 444 */ + "toNativePhpTypeNode\0" /* 476 */ + "hasTemplateOrLateResolvableType"; /* 496 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ @@ -91,11 +92,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(266, 0, 75), /* looseCompare $type */ reg::packed(271, 0, 282), /* looseCompare $phpVersion */ reg::packed(46, 0, 305), /* looseCompare return */ - reg::packed(343, 0, 75), /* exponentiate $exponent */ - reg::packed(46, 0, 75), /* exponentiate return */ + reg::packed(343, 0, 75), /* arithmeticPower $exponent */ + reg::packed(271, 0, 282), /* arithmeticPower $phpVersion */ + reg::packed(46, 0, 75), /* arithmeticPower return */ reg::packed(46, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(46, 0, 380), /* toPhpDocNode return */ - reg::packed(432, 0, 441), /* toNativePhpTypeNode $position */ + reg::packed(46, 0, 383), /* toPhpDocNode return */ + reg::packed(435, 0, 444), /* toNativePhpTypeNode $position */ reg::packed(46, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -118,11 +120,11 @@ inline constexpr sigtab::Sig toCoercedArgumentType = { { 194 /* toCoercedArgumen inline constexpr sigtab::Sig isOffsetAccessLegal = { { 237 /* isOffsetAccessLegal */, 0, 13, 0, 13, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isScalar = { { 257 /* isScalar */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig looseCompare = { { 330 /* looseCompare */, 2, 15, 2, 17, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 352 /* exponentiate */, 1, 18, 1, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 365 /* getFiniteTypes */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 419 /* toPhpDocNode */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 473 /* toNativePhpTypeNode */, 1, 22, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 493 /* hasTemplateOrLateResolvableType */, 0, 23, 0, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 352 /* arithmeticPower */, 2, 18, 2, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 368 /* getFiniteTypes */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 422 /* toPhpDocNode */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 476 /* toNativePhpTypeNode */, 1, 23, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 496 /* hasTemplateOrLateResolvableType */, 0, 24, 0, 24, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ResourceType diff --git a/turbo-ext/src/generated/StaticType.h b/turbo-ext/src/generated/StaticType.h index a0a6cd7e894..a685efab6cc 100644 --- a/turbo-ext/src/generated/StaticType.h +++ b/turbo-ext/src/generated/StaticType.h @@ -38,6 +38,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_generic(cls); pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -228,14 +229,14 @@ inline constexpr char strings[] = "typeToRemove\0" /* 3203 */ "tryRemove\0" /* 3216 */ "exponent\0" /* 3226 */ - "exponentiate\0" /* 3235 */ - "getFiniteTypes\0" /* 3248 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3263 */ - "toPhpDocNode\0" /* 3302 */ - "position\0" /* 3315 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3324 */ - "toNativePhpTypeNode\0" /* 3356 */ - "hasTemplateOrLateResolvableType"; /* 3376 */ + "arithmeticPower\0" /* 3235 */ + "getFiniteTypes\0" /* 3251 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3266 */ + "toPhpDocNode\0" /* 3305 */ + "position\0" /* 3318 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 3327 */ + "toNativePhpTypeNode\0" /* 3359 */ + "hasTemplateOrLateResolvableType"; /* 3379 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 16), /* __construct $classReflection */ reg::packed(51, MAY_BE_NULL, 66, false, false, 84), /* __construct $subtractedType */ @@ -441,11 +442,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(101, MAY_BE_NULL, 66), /* getSubtractedType return */ reg::packed(3203, 0, 66), /* tryRemove $typeToRemove */ reg::packed(101, MAY_BE_NULL, 66), /* tryRemove return */ - reg::packed(3226, 0, 66), /* exponentiate $exponent */ - reg::packed(101, 0, 66), /* exponentiate return */ + reg::packed(3226, 0, 66), /* arithmeticPower $exponent */ + reg::packed(2613, 0, 2624), /* arithmeticPower $phpVersion */ + reg::packed(101, 0, 66), /* arithmeticPower return */ reg::packed(101, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(101, 0, 3263), /* toPhpDocNode return */ - reg::packed(3315, 0, 3324), /* toNativePhpTypeNode $position */ + reg::packed(101, 0, 3266), /* toPhpDocNode return */ + reg::packed(3318, 0, 3327), /* toNativePhpTypeNode $position */ reg::packed(101, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -582,11 +584,11 @@ inline constexpr sigtab::Sig getTypeWithoutSubtractedType = { { 3135 /* getTypeW inline constexpr sigtab::Sig changeSubtractedType = { { 3164 /* changeSubtractedType */, 1, 199, 1, 200, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getSubtractedType = { { 3185 /* getSubtractedType */, 0, 201, 0, 201, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 3216 /* tryRemove */, 1, 202, 1, 203, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 3235 /* exponentiate */, 1, 204, 1, 205, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3248 /* getFiniteTypes */, 0, 206, 0, 206, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3302 /* toPhpDocNode */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3356 /* toNativePhpTypeNode */, 1, 208, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3376 /* hasTemplateOrLateResolvableType */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 3235 /* arithmeticPower */, 2, 204, 2, 206, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 3251 /* getFiniteTypes */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 3305 /* toPhpDocNode */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3359 /* toNativePhpTypeNode */, 1, 209, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3379 /* hasTemplateOrLateResolvableType */, 0, 210, 0, 210, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StaticType diff --git a/turbo-ext/src/generated/StrictMixedType.h b/turbo-ext/src/generated/StrictMixedType.h index e63d423ce32..26c8f4839f0 100644 --- a/turbo-ext/src/generated/StrictMixedType.h +++ b/turbo-ext/src/generated/StrictMixedType.h @@ -28,6 +28,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_non_iterable(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -166,14 +167,14 @@ inline constexpr char strings[] = "right\0" /* 2415 */ "traverseSimultaneously\0" /* 2421 */ "exponent\0" /* 2444 */ - "exponentiate\0" /* 2453 */ - "getFiniteTypes\0" /* 2466 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 2481 */ - "toPhpDocNode\0" /* 2520 */ - "position\0" /* 2533 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 2542 */ - "toNativePhpTypeNode\0" /* 2574 */ - "hasTemplateOrLateResolvableType"; /* 2594 */ + "arithmeticPower\0" /* 2453 */ + "getFiniteTypes\0" /* 2469 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 2484 */ + "toPhpDocNode\0" /* 2523 */ + "position\0" /* 2536 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 2545 */ + "toNativePhpTypeNode\0" /* 2577 */ + "hasTemplateOrLateResolvableType"; /* 2597 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(0, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -317,11 +318,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(2415, 0, 92), /* traverseSimultaneously $right */ reg::packed(2403, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(0, 0, 92), /* traverseSimultaneously return */ - reg::packed(2444, 0, 92), /* exponentiate $exponent */ - reg::packed(0, 0, 92), /* exponentiate return */ + reg::packed(2444, 0, 92), /* arithmeticPower $exponent */ + reg::packed(1521, 0, 1532), /* arithmeticPower $phpVersion */ + reg::packed(0, 0, 92), /* arithmeticPower return */ reg::packed(0, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(0, 0, 2481), /* toPhpDocNode return */ - reg::packed(2533, 0, 2542), /* toNativePhpTypeNode $position */ + reg::packed(0, 0, 2484), /* toPhpDocNode return */ + reg::packed(2536, 0, 2545), /* toNativePhpTypeNode $position */ reg::packed(0, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -418,11 +420,11 @@ inline constexpr sigtab::Sig getEnumCases = { { 2335 /* getEnumCases */, 0, 135, inline constexpr sigtab::Sig getEnumCaseObject = { { 2385 /* getEnumCaseObject */, 0, 136, 0, 136, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 2406 /* traverse */, 1, 137, 1, 138, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 2421 /* traverseSimultaneously */, 2, 139, 2, 141, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 2453 /* exponentiate */, 1, 142, 1, 143, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 2466 /* getFiniteTypes */, 0, 144, 0, 144, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 2520 /* toPhpDocNode */, 0, 145, 0, 145, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 2574 /* toNativePhpTypeNode */, 1, 146, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 2594 /* hasTemplateOrLateResolvableType */, 0, 147, 0, 147, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 2453 /* arithmeticPower */, 2, 142, 2, 144, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 2469 /* getFiniteTypes */, 0, 145, 0, 145, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 2523 /* toPhpDocNode */, 0, 146, 0, 146, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 2577 /* toNativePhpTypeNode */, 1, 147, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 2597 /* hasTemplateOrLateResolvableType */, 0, 148, 0, 148, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StrictMixedType diff --git a/turbo-ext/src/generated/StringType.h b/turbo-ext/src/generated/StringType.h index 920faf2ff4f..c7f38bb7d8e 100644 --- a/turbo-ext/src/generated/StringType.h +++ b/turbo-ext/src/generated/StringType.h @@ -31,6 +31,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_generic(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -96,13 +97,13 @@ inline constexpr char strings[] = "tryRemove\0" /* 825 */ "getFiniteTypes\0" /* 835 */ "exponent\0" /* 850 */ - "exponentiate\0" /* 859 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 872 */ - "toPhpDocNode\0" /* 911 */ - "position\0" /* 924 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 933 */ - "toNativePhpTypeNode\0" /* 965 */ - "hasTemplateOrLateResolvableType"; /* 985 */ + "arithmeticPower\0" /* 859 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 875 */ + "toPhpDocNode\0" /* 914 */ + "position\0" /* 927 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 936 */ + "toNativePhpTypeNode\0" /* 968 */ + "hasTemplateOrLateResolvableType"; /* 988 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ @@ -161,10 +162,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(812, 0, 146), /* tryRemove $typeToRemove */ reg::packed(46, MAY_BE_NULL, 146), /* tryRemove return */ reg::packed(46, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(850, 0, 146), /* exponentiate $exponent */ - reg::packed(46, 0, 146), /* exponentiate return */ - reg::packed(46, 0, 872), /* toPhpDocNode return */ - reg::packed(924, 0, 933), /* toNativePhpTypeNode $position */ + reg::packed(850, 0, 146), /* arithmeticPower $exponent */ + reg::packed(719, 0, 730), /* arithmeticPower $phpVersion */ + reg::packed(46, 0, 146), /* arithmeticPower return */ + reg::packed(46, 0, 875), /* toPhpDocNode return */ + reg::packed(927, 0, 936), /* toNativePhpTypeNode $position */ reg::packed(46, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -214,10 +216,10 @@ inline constexpr sigtab::Sig looseCompare = { { 778 /* looseCompare */, 2, 49, 2 inline constexpr sigtab::Sig hasMethod = { { 802 /* hasMethod */, 1, 52, 1, 53, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 825 /* tryRemove */, 1, 54, 1, 55, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFiniteTypes = { { 835 /* getFiniteTypes */, 0, 56, 0, 56, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 859 /* exponentiate */, 1, 57, 1, 58, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 911 /* toPhpDocNode */, 0, 59, 0, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 965 /* toNativePhpTypeNode */, 1, 60, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 985 /* hasTemplateOrLateResolvableType */, 0, 61, 0, 61, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 859 /* arithmeticPower */, 2, 57, 2, 59, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 914 /* toPhpDocNode */, 0, 60, 0, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 968 /* toNativePhpTypeNode */, 1, 61, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 988 /* hasTemplateOrLateResolvableType */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StringType diff --git a/turbo-ext/src/generated/UnionType.h b/turbo-ext/src/generated/UnionType.h index a75507d1bd2..2b88aee4800 100644 --- a/turbo-ext/src/generated/UnionType.h +++ b/turbo-ext/src/generated/UnionType.h @@ -42,6 +42,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -256,23 +257,23 @@ inline constexpr char strings[] = "typeToRemove\0" /* 3547 */ "tryRemove\0" /* 3560 */ "exponent\0" /* 3570 */ - "exponentiate\0" /* 3579 */ - "getFiniteTypes\0" /* 3592 */ - "getResult\0" /* 3607 */ - "unionResults\0" /* 3617 */ - "notBenevolentUnionResults\0" /* 3630 */ - "getType\0" /* 3656 */ - "unionTypes\0" /* 3664 */ - "getValues\0" /* 3675 */ - "criteria\0" /* 3685 */ - "pickFromTypes\0" /* 3694 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 3708 */ - "toPhpDocNode\0" /* 3747 */ - "position\0" /* 3760 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 3769 */ - "toNativePhpTypeNode\0" /* 3801 */ - "notBenevolentPickFromTypes\0" /* 3821 */ - "hasTemplateOrLateResolvableType"; /* 3848 */ + "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 */ 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 */ @@ -512,21 +513,22 @@ inline constexpr reg::PackedArg args[] = { reg::packed(35, 0, 54), /* traverseSimultaneously return */ reg::packed(3547, 0, 54), /* tryRemove $typeToRemove */ reg::packed(35, MAY_BE_NULL, 54), /* tryRemove return */ - reg::packed(3570, 0, 54), /* exponentiate $exponent */ - reg::packed(35, 0, 54), /* exponentiate return */ + reg::packed(3570, 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(3607, MAY_BE_CALLABLE), /* unionResults $getResult */ + reg::packed(3610, MAY_BE_CALLABLE), /* unionResults $getResult */ reg::packed(35, 0, 426), /* unionResults return */ - reg::packed(3607, MAY_BE_CALLABLE), /* notBenevolentUnionResults $getResult */ + reg::packed(3610, MAY_BE_CALLABLE), /* notBenevolentUnionResults $getResult */ reg::packed(35, 0, 426), /* notBenevolentUnionResults return */ - reg::packed(3656, MAY_BE_CALLABLE), /* unionTypes $getType */ + reg::packed(3659, MAY_BE_CALLABLE), /* unionTypes $getType */ reg::packed(35, 0, 54), /* unionTypes return */ - reg::packed(3675, MAY_BE_CALLABLE), /* pickFromTypes $getValues */ - reg::packed(3685, MAY_BE_CALLABLE), /* pickFromTypes $criteria */ + reg::packed(3678, MAY_BE_CALLABLE), /* pickFromTypes $getValues */ + reg::packed(3688, MAY_BE_CALLABLE), /* pickFromTypes $criteria */ reg::packed(35, MAY_BE_ARRAY), /* pickFromTypes return */ - reg::packed(35, 0, 3708), /* toPhpDocNode return */ - reg::packed(3760, 0, 3769), /* toNativePhpTypeNode $position */ - reg::packed(3675, MAY_BE_CALLABLE), /* notBenevolentPickFromTypes $getValues */ + 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, MAY_BE_ARRAY), /* notBenevolentPickFromTypes return */ reg::packed(35, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; @@ -677,16 +679,16 @@ inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3482 /* getReferen 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 exponentiate = { { 3579 /* exponentiate */, 1, 238, 1, 239, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 3592 /* getFiniteTypes */, 0, 240, 0, 240, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig unionResults = { { 3617 /* unionResults */, 1, 241, 1, 242, ZEND_ACC_PROTECTED } }; -inline constexpr sigtab::Sig notBenevolentUnionResults = { { 3630 /* notBenevolentUnionResults */, 1, 243, 1, 244, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig unionTypes = { { 3664 /* unionTypes */, 1, 245, 1, 246, ZEND_ACC_PROTECTED } }; -inline constexpr sigtab::Sig pickFromTypes = { { 3694 /* pickFromTypes */, 2, 247, 2, 249, ZEND_ACC_PROTECTED } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 3747 /* toPhpDocNode */, 0, 250, 0, 250, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3801 /* toNativePhpTypeNode */, 1, 251, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig notBenevolentPickFromTypes = { { 3821 /* notBenevolentPickFromTypes */, 1, 252, 1, 253, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3848 /* hasTemplateOrLateResolvableType */, 0, 254, 0, 254, 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 } }; } // namespace sig } // namespace ptdecl::UnionType diff --git a/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h b/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h index d07db6cc91f..6b53eabc8c5 100644 --- a/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h +++ b/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h @@ -29,6 +29,12 @@ inline void declareProperties(reg::Class &cls) cls.property("initialType", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); } +/* the shared registrars of the traits the twin uses, in its own order (a used trait's own traits after it) */ +inline void registerTraits(reg::Class &cls) +{ + pt_type_trait_operator(cls); +} + /* the string and parameter tables the signatures below index into (see reg::Sig) */ namespace sigtab { inline constexpr char strings[] = @@ -185,67 +191,67 @@ inline constexpr char strings[] = "getEnumCaseObject\0" /* 2673 */ "getFiniteTypes\0" /* 2691 */ "exponent\0" /* 2706 */ - "exponentiate\0" /* 2715 */ - "isCallable\0" /* 2728 */ - "getCallableParametersAcceptors\0" /* 2739 */ - "isCloneable\0" /* 2770 */ - "PHPStan\\Type\\BooleanType\0" /* 2782 */ - "toBoolean\0" /* 2807 */ - "toNumber\0" /* 2817 */ - "toBitwiseNotType\0" /* 2826 */ - "toGetClassResultType\0" /* 2843 */ - "reflectionProvider\0" /* 2864 */ - "PHPStan\\Reflection\\ReflectionProvider\0" /* 2883 */ - "toClassConstantType\0" /* 2921 */ - "PHPStan\\Type\\ClassNameToObjectTypeResult\0" /* 2941 */ - "toObjectTypeForInstanceofCheck\0" /* 2982 */ - "objectOrClassType\0" /* 3013 */ - "allowString\0" /* 3031 */ - "allowSameClass\0" /* 3043 */ - "toObjectTypeForIsACheck\0" /* 3058 */ - "toInteger\0" /* 3082 */ - "toFloat\0" /* 3092 */ - "toString\0" /* 3100 */ - "toArray\0" /* 3109 */ - "toArrayKey\0" /* 3117 */ - "isSmallerThan\0" /* 3128 */ - "isSmallerThanOrEqual\0" /* 3142 */ - "isConstantValue\0" /* 3163 */ - "isConstantScalarValue\0" /* 3179 */ - "getConstantScalarTypes\0" /* 3201 */ - "getConstantScalarValues\0" /* 3224 */ - "isNull\0" /* 3248 */ - "isTrue\0" /* 3255 */ - "isFalse\0" /* 3262 */ - "isBoolean\0" /* 3270 */ - "isFloat\0" /* 3280 */ - "isInteger\0" /* 3288 */ - "isString\0" /* 3298 */ - "isNumericString\0" /* 3307 */ - "isDecimalIntegerString\0" /* 3323 */ - "isNonEmptyString\0" /* 3346 */ - "isNonFalsyString\0" /* 3363 */ - "isLiteralString\0" /* 3380 */ - "isLowercaseString\0" /* 3396 */ - "isUppercaseString\0" /* 3414 */ - "isClassString\0" /* 3432 */ - "isVoid\0" /* 3446 */ - "isScalar\0" /* 3453 */ - "looseCompare\0" /* 3462 */ - "getSmallerType\0" /* 3475 */ - "getSmallerOrEqualType\0" /* 3490 */ - "getGreaterType\0" /* 3512 */ - "getGreaterOrEqualType\0" /* 3527 */ - "ancestorClassName\0" /* 3549 */ - "templateTypeName\0" /* 3567 */ - "getTemplateType\0" /* 3584 */ - "receivedType\0" /* 3600 */ - "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3613 */ - "inferTemplateTypes\0" /* 3650 */ - "positionVariance\0" /* 3669 */ - "PHPStan\\Type\\Generic\\TemplateTypeVariance\0" /* 3686 */ - "getReferencedTemplateTypes\0" /* 3728 */ - "toAbsoluteNumber"; /* 3755 */ + "arithmeticPower\0" /* 2715 */ + "isCallable\0" /* 2731 */ + "getCallableParametersAcceptors\0" /* 2742 */ + "isCloneable\0" /* 2773 */ + "PHPStan\\Type\\BooleanType\0" /* 2785 */ + "toBoolean\0" /* 2810 */ + "toNumber\0" /* 2820 */ + "toBitwiseNotType\0" /* 2829 */ + "toGetClassResultType\0" /* 2846 */ + "reflectionProvider\0" /* 2867 */ + "PHPStan\\Reflection\\ReflectionProvider\0" /* 2886 */ + "toClassConstantType\0" /* 2924 */ + "PHPStan\\Type\\ClassNameToObjectTypeResult\0" /* 2944 */ + "toObjectTypeForInstanceofCheck\0" /* 2985 */ + "objectOrClassType\0" /* 3016 */ + "allowString\0" /* 3034 */ + "allowSameClass\0" /* 3046 */ + "toObjectTypeForIsACheck\0" /* 3061 */ + "toInteger\0" /* 3085 */ + "toFloat\0" /* 3095 */ + "toString\0" /* 3103 */ + "toArray\0" /* 3112 */ + "toArrayKey\0" /* 3120 */ + "isSmallerThan\0" /* 3131 */ + "isSmallerThanOrEqual\0" /* 3145 */ + "isConstantValue\0" /* 3166 */ + "isConstantScalarValue\0" /* 3182 */ + "getConstantScalarTypes\0" /* 3204 */ + "getConstantScalarValues\0" /* 3227 */ + "isNull\0" /* 3251 */ + "isTrue\0" /* 3258 */ + "isFalse\0" /* 3265 */ + "isBoolean\0" /* 3273 */ + "isFloat\0" /* 3283 */ + "isInteger\0" /* 3291 */ + "isString\0" /* 3301 */ + "isNumericString\0" /* 3310 */ + "isDecimalIntegerString\0" /* 3326 */ + "isNonEmptyString\0" /* 3349 */ + "isNonFalsyString\0" /* 3366 */ + "isLiteralString\0" /* 3383 */ + "isLowercaseString\0" /* 3399 */ + "isUppercaseString\0" /* 3417 */ + "isClassString\0" /* 3435 */ + "isVoid\0" /* 3449 */ + "isScalar\0" /* 3456 */ + "looseCompare\0" /* 3465 */ + "getSmallerType\0" /* 3478 */ + "getSmallerOrEqualType\0" /* 3493 */ + "getGreaterType\0" /* 3515 */ + "getGreaterOrEqualType\0" /* 3530 */ + "ancestorClassName\0" /* 3552 */ + "templateTypeName\0" /* 3570 */ + "getTemplateType\0" /* 3587 */ + "receivedType\0" /* 3603 */ + "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3616 */ + "inferTemplateTypes\0" /* 3653 */ + "positionVariance\0" /* 3672 */ + "PHPStan\\Type\\Generic\\TemplateTypeVariance\0" /* 3689 */ + "getReferencedTemplateTypes\0" /* 3731 */ + "toAbsoluteNumber"; /* 3758 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* __construct $site */ reg::packed(25, 0, 38), /* __construct $templateType */ @@ -422,23 +428,24 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, MAY_BE_ARRAY), /* getEnumCases return */ reg::packed(114, MAY_BE_NULL, 2636), /* getEnumCaseObject return */ reg::packed(114, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(2706, 0, 84), /* exponentiate $exponent */ - reg::packed(114, 0, 84), /* exponentiate return */ + reg::packed(2706, 0, 84), /* arithmeticPower $exponent */ + reg::packed(536, 0, 547), /* arithmeticPower $phpVersion */ + reg::packed(114, 0, 84), /* arithmeticPower return */ reg::packed(114, 0, 570), /* isCallable return */ reg::packed(1167, 0, 1173), /* getCallableParametersAcceptors $scope */ reg::packed(114, MAY_BE_ARRAY), /* getCallableParametersAcceptors return */ reg::packed(114, 0, 570), /* isCloneable return */ - reg::packed(114, 0, 2782), /* toBoolean return */ + reg::packed(114, 0, 2785), /* toBoolean return */ reg::packed(114, 0, 84), /* toNumber return */ reg::packed(114, 0, 84), /* toBitwiseNotType return */ reg::packed(114, 0, 84), /* toGetClassResultType return */ - reg::packed(2864, 0, 2883), /* toClassConstantType $reflectionProvider */ + reg::packed(2867, 0, 2886), /* toClassConstantType $reflectionProvider */ reg::packed(114, 0, 84), /* toClassConstantType return */ - reg::packed(114, 0, 2941), /* toObjectTypeForInstanceofCheck return */ - reg::packed(3013, 0, 84), /* toObjectTypeForIsACheck $objectOrClassType */ - reg::packed(3031, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowString */ - reg::packed(3043, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowSameClass */ - reg::packed(114, 0, 2941), /* toObjectTypeForIsACheck return */ + reg::packed(114, 0, 2944), /* toObjectTypeForInstanceofCheck return */ + reg::packed(3016, 0, 84), /* toObjectTypeForIsACheck $objectOrClassType */ + reg::packed(3034, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowString */ + reg::packed(3046, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowSameClass */ + reg::packed(114, 0, 2944), /* toObjectTypeForIsACheck return */ reg::packed(114, 0, 84), /* toInteger return */ reg::packed(114, 0, 84), /* toFloat return */ reg::packed(114, 0, 84), /* toString return */ @@ -473,7 +480,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 570), /* isScalar return */ reg::packed(274, 0, 84), /* looseCompare $type */ reg::packed(536, 0, 547), /* looseCompare $phpVersion */ - reg::packed(114, 0, 2782), /* looseCompare return */ + reg::packed(114, 0, 2785), /* looseCompare return */ reg::packed(536, 0, 547), /* getSmallerType $phpVersion */ reg::packed(114, 0, 84), /* getSmallerType return */ reg::packed(536, 0, 547), /* getSmallerOrEqualType $phpVersion */ @@ -482,12 +489,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 84), /* getGreaterType return */ reg::packed(536, 0, 547), /* getGreaterOrEqualType $phpVersion */ reg::packed(114, 0, 84), /* getGreaterOrEqualType return */ - reg::packed(3549, MAY_BE_STRING), /* getTemplateType $ancestorClassName */ - reg::packed(3567, MAY_BE_STRING), /* getTemplateType $templateTypeName */ + reg::packed(3552, MAY_BE_STRING), /* getTemplateType $ancestorClassName */ + reg::packed(3570, MAY_BE_STRING), /* getTemplateType $templateTypeName */ reg::packed(114, 0, 84), /* getTemplateType return */ - reg::packed(3600, 0, 84), /* inferTemplateTypes $receivedType */ - reg::packed(114, 0, 3613), /* inferTemplateTypes return */ - reg::packed(3669, 0, 3686), /* getReferencedTemplateTypes $positionVariance */ + reg::packed(3603, 0, 84), /* inferTemplateTypes $receivedType */ + reg::packed(114, 0, 3616), /* inferTemplateTypes return */ + reg::packed(3672, 0, 3689), /* getReferencedTemplateTypes $positionVariance */ reg::packed(114, MAY_BE_ARRAY), /* getReferencedTemplateTypes return */ reg::packed(114, 0, 84), /* toAbsoluteNumber return */ }; @@ -596,54 +603,54 @@ inline constexpr sigtab::Sig filterArrayRemovingFalsey = { { 2597 /* filterArray inline constexpr sigtab::Sig getEnumCases = { { 2623 /* getEnumCases */, 0, 172, 0, 172, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getEnumCaseObject = { { 2673 /* getEnumCaseObject */, 0, 173, 0, 173, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFiniteTypes = { { 2691 /* getFiniteTypes */, 0, 174, 0, 174, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 2715 /* exponentiate */, 1, 175, 1, 176, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isCallable = { { 2728 /* isCallable */, 0, 177, 0, 177, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getCallableParametersAcceptors = { { 2739 /* getCallableParametersAcceptors */, 1, 178, 1, 179, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isCloneable = { { 2770 /* isCloneable */, 0, 180, 0, 180, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBoolean = { { 2807 /* toBoolean */, 0, 181, 0, 181, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNumber = { { 2817 /* toNumber */, 0, 182, 0, 182, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 2826 /* toBitwiseNotType */, 0, 183, 0, 183, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toGetClassResultType = { { 2843 /* toGetClassResultType */, 0, 184, 0, 184, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toClassConstantType = { { 2921 /* toClassConstantType */, 1, 185, 1, 186, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toObjectTypeForInstanceofCheck = { { 2982 /* toObjectTypeForInstanceofCheck */, 0, 187, 0, 187, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toObjectTypeForIsACheck = { { 3058 /* toObjectTypeForIsACheck */, 3, 188, 3, 191, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toInteger = { { 3082 /* toInteger */, 0, 192, 0, 192, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toFloat = { { 3092 /* toFloat */, 0, 193, 0, 193, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 3100 /* toString */, 0, 194, 0, 194, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArray = { { 3109 /* toArray */, 0, 195, 0, 195, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toArrayKey = { { 3117 /* toArrayKey */, 0, 196, 0, 196, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSmallerThan = { { 3128 /* isSmallerThan */, 2, 197, 2, 199, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 3142 /* isSmallerThanOrEqual */, 2, 200, 2, 202, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isConstantValue = { { 3163 /* isConstantValue */, 0, 203, 0, 203, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isConstantScalarValue = { { 3179 /* isConstantScalarValue */, 0, 204, 0, 204, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantScalarTypes = { { 3201 /* getConstantScalarTypes */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantScalarValues = { { 3224 /* getConstantScalarValues */, 0, 206, 0, 206, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNull = { { 3248 /* isNull */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isTrue = { { 3255 /* isTrue */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isFalse = { { 3262 /* isFalse */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isBoolean = { { 3270 /* isBoolean */, 0, 210, 0, 210, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isFloat = { { 3280 /* isFloat */, 0, 211, 0, 211, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInteger = { { 3288 /* isInteger */, 0, 212, 0, 212, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isString = { { 3298 /* isString */, 0, 213, 0, 213, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNumericString = { { 3307 /* isNumericString */, 0, 214, 0, 214, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isDecimalIntegerString = { { 3323 /* isDecimalIntegerString */, 0, 215, 0, 215, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNonEmptyString = { { 3346 /* isNonEmptyString */, 0, 216, 0, 216, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isNonFalsyString = { { 3363 /* isNonFalsyString */, 0, 217, 0, 217, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isLiteralString = { { 3380 /* isLiteralString */, 0, 218, 0, 218, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isLowercaseString = { { 3396 /* isLowercaseString */, 0, 219, 0, 219, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isUppercaseString = { { 3414 /* isUppercaseString */, 0, 220, 0, 220, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isClassString = { { 3432 /* isClassString */, 0, 221, 0, 221, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isVoid = { { 3446 /* isVoid */, 0, 222, 0, 222, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isScalar = { { 3453 /* isScalar */, 0, 223, 0, 223, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 3462 /* looseCompare */, 2, 224, 2, 226, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerType = { { 3475 /* getSmallerType */, 1, 227, 1, 228, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerOrEqualType = { { 3490 /* getSmallerOrEqualType */, 1, 229, 1, 230, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterType = { { 3512 /* getGreaterType */, 1, 231, 1, 232, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterOrEqualType = { { 3527 /* getGreaterOrEqualType */, 1, 233, 1, 234, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getTemplateType = { { 3584 /* getTemplateType */, 2, 235, 2, 237, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig inferTemplateTypes = { { 3650 /* inferTemplateTypes */, 1, 238, 1, 239, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3728 /* getReferencedTemplateTypes */, 1, 240, 1, 241, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 3755 /* toAbsoluteNumber */, 0, 242, 0, 242, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 2715 /* arithmeticPower */, 2, 175, 2, 177, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isCallable = { { 2731 /* isCallable */, 0, 178, 0, 178, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getCallableParametersAcceptors = { { 2742 /* getCallableParametersAcceptors */, 1, 179, 1, 180, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isCloneable = { { 2773 /* isCloneable */, 0, 181, 0, 181, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBoolean = { { 2810 /* toBoolean */, 0, 182, 0, 182, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNumber = { { 2820 /* toNumber */, 0, 183, 0, 183, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 2829 /* toBitwiseNotType */, 0, 184, 0, 184, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toGetClassResultType = { { 2846 /* toGetClassResultType */, 0, 185, 0, 185, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toClassConstantType = { { 2924 /* toClassConstantType */, 1, 186, 1, 187, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toObjectTypeForInstanceofCheck = { { 2985 /* toObjectTypeForInstanceofCheck */, 0, 188, 0, 188, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toObjectTypeForIsACheck = { { 3061 /* toObjectTypeForIsACheck */, 3, 189, 3, 192, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 3085 /* toInteger */, 0, 193, 0, 193, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 3095 /* toFloat */, 0, 194, 0, 194, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 3103 /* toString */, 0, 195, 0, 195, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArray = { { 3112 /* toArray */, 0, 196, 0, 196, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 3120 /* toArrayKey */, 0, 197, 0, 197, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThan = { { 3131 /* isSmallerThan */, 2, 198, 2, 200, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 3145 /* isSmallerThanOrEqual */, 2, 201, 2, 203, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConstantValue = { { 3166 /* isConstantValue */, 0, 204, 0, 204, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConstantScalarValue = { { 3182 /* isConstantScalarValue */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantScalarTypes = { { 3204 /* getConstantScalarTypes */, 0, 206, 0, 206, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantScalarValues = { { 3227 /* getConstantScalarValues */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNull = { { 3251 /* isNull */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isTrue = { { 3258 /* isTrue */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFalse = { { 3265 /* isFalse */, 0, 210, 0, 210, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isBoolean = { { 3273 /* isBoolean */, 0, 211, 0, 211, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFloat = { { 3283 /* isFloat */, 0, 212, 0, 212, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInteger = { { 3291 /* isInteger */, 0, 213, 0, 213, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isString = { { 3301 /* isString */, 0, 214, 0, 214, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNumericString = { { 3310 /* isNumericString */, 0, 215, 0, 215, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isDecimalIntegerString = { { 3326 /* isDecimalIntegerString */, 0, 216, 0, 216, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNonEmptyString = { { 3349 /* isNonEmptyString */, 0, 217, 0, 217, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNonFalsyString = { { 3366 /* isNonFalsyString */, 0, 218, 0, 218, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isLiteralString = { { 3383 /* isLiteralString */, 0, 219, 0, 219, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isLowercaseString = { { 3399 /* isLowercaseString */, 0, 220, 0, 220, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUppercaseString = { { 3417 /* isUppercaseString */, 0, 221, 0, 221, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isClassString = { { 3435 /* isClassString */, 0, 222, 0, 222, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isVoid = { { 3449 /* isVoid */, 0, 223, 0, 223, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isScalar = { { 3456 /* isScalar */, 0, 224, 0, 224, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 3465 /* looseCompare */, 2, 225, 2, 227, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerType = { { 3478 /* getSmallerType */, 1, 228, 1, 229, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerOrEqualType = { { 3493 /* getSmallerOrEqualType */, 1, 230, 1, 231, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterType = { { 3515 /* getGreaterType */, 1, 232, 1, 233, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterOrEqualType = { { 3530 /* getGreaterOrEqualType */, 1, 234, 1, 235, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTemplateType = { { 3587 /* getTemplateType */, 2, 236, 2, 238, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig inferTemplateTypes = { { 3653 /* inferTemplateTypes */, 1, 239, 1, 240, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3731 /* getReferencedTemplateTypes */, 1, 241, 1, 242, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 3758 /* toAbsoluteNumber */, 0, 243, 0, 243, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::UnresolvedTemplateArgumentType diff --git a/turbo-ext/src/generated/ValueOfType.h b/turbo-ext/src/generated/ValueOfType.h index 160f818d0a0..b1ef2072275 100644 --- a/turbo-ext/src/generated/ValueOfType.h +++ b/turbo-ext/src/generated/ValueOfType.h @@ -30,6 +30,7 @@ inline void declareProperties(reg::Class &cls) inline void registerTraits(reg::Class &cls) { pt_type_trait_late_resolvable(cls); + pt_type_trait_operator(cls); pt_type_trait_non_generalizable(cls); } diff --git a/turbo-ext/src/generated/VoidType.h b/turbo-ext/src/generated/VoidType.h index 362d155c814..042fca75052 100644 --- a/turbo-ext/src/generated/VoidType.h +++ b/turbo-ext/src/generated/VoidType.h @@ -32,6 +32,7 @@ inline void registerTraits(reg::Class &cls) pt_type_trait_undecided_comparison(cls); pt_type_trait_non_removeable(cls); pt_type_trait_non_generalizable(cls); + pt_type_trait_operator(cls); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -96,14 +97,14 @@ inline constexpr char strings[] = "right\0" /* 844 */ "traverseSimultaneously\0" /* 850 */ "exponent\0" /* 873 */ - "exponentiate\0" /* 882 */ - "getFiniteTypes\0" /* 895 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 910 */ - "toPhpDocNode\0" /* 949 */ - "position\0" /* 962 */ - "PHPStan\\Type\\NativeTypePosition\0" /* 971 */ - "toNativePhpTypeNode\0" /* 1003 */ - "hasTemplateOrLateResolvableType"; /* 1023 */ + "arithmeticPower\0" /* 882 */ + "getFiniteTypes\0" /* 898 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 913 */ + "toPhpDocNode\0" /* 952 */ + "position\0" /* 965 */ + "PHPStan\\Type\\NativeTypePosition\0" /* 974 */ + "toNativePhpTypeNode\0" /* 1006 */ + "hasTemplateOrLateResolvableType"; /* 1026 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -159,11 +160,12 @@ inline constexpr reg::PackedArg args[] = { reg::packed(844, 0, 85), /* traverseSimultaneously $right */ reg::packed(832, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(12, 0, 85), /* traverseSimultaneously return */ - reg::packed(873, 0, 85), /* exponentiate $exponent */ - reg::packed(12, 0, 85), /* exponentiate return */ + reg::packed(873, 0, 85), /* arithmeticPower $exponent */ + reg::packed(760, 0, 771), /* arithmeticPower $phpVersion */ + reg::packed(12, 0, 85), /* arithmeticPower return */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(12, 0, 910), /* toPhpDocNode return */ - reg::packed(962, 0, 971), /* toNativePhpTypeNode $position */ + reg::packed(12, 0, 913), /* toPhpDocNode return */ + reg::packed(965, 0, 974), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ }; using Sig = reg::Sig; @@ -215,11 +217,11 @@ inline constexpr sigtab::Sig isScalar = { { 751 /* isScalar */, 0, 45, 0, 45, ZE inline constexpr sigtab::Sig looseCompare = { { 819 /* looseCompare */, 2, 46, 2, 48, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 835 /* traverse */, 1, 49, 1, 50, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 850 /* traverseSimultaneously */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig exponentiate = { { 882 /* exponentiate */, 1, 54, 1, 55, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 895 /* getFiniteTypes */, 0, 56, 0, 56, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 949 /* toPhpDocNode */, 0, 57, 0, 57, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1003 /* toNativePhpTypeNode */, 1, 58, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1023 /* hasTemplateOrLateResolvableType */, 0, 59, 0, 59, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 882 /* arithmeticPower */, 2, 54, 2, 56, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 898 /* getFiniteTypes */, 0, 57, 0, 57, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 952 /* toPhpDocNode */, 0, 58, 0, 58, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1006 /* toNativePhpTypeNode */, 1, 59, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1026 /* hasTemplateOrLateResolvableType */, 0, 60, 0, 60, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::VoidType diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 6c0e26f9c3a..92c13f8125e 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -4089,6 +4089,39 @@ zv::Val pt_initializer_expr_type_resolver_get_bitwise_not_type(zval *resolver, z /* }}} */ +/* {{{ OperatorTypeHelper.cpp — the shadowing PHPStan\Type\OperatorTypeHelper + * (a final class of static methods) */ + +extern zend_class_entry *pt_ce_operator_type_helper; +/* PhpVersionFactory::MAX_PHP_VERSION — the PhpVersion OperatorTypeTrait's + * deprecated exponentiate() hands arithmeticPower() (smoke.php holds it + * against the class constant) */ +#define PT_PHP_VERSION_FACTORY_MAX_PHP_VERSION 80699 +/* the binary operators of the Type methods OperatorTypeTrait delegates */ +enum pt_operator_type_op : uint8_t +{ + PT_OPERATOR_ADD, + PT_OPERATOR_SUBTRACT, + PT_OPERATOR_MULTIPLY, + PT_OPERATOR_DIVIDE, + PT_OPERATOR_INT_DIVIDE, + PT_OPERATOR_MODULO, + PT_OPERATOR_BITWISE_AND, + PT_OPERATOR_BITWISE_OR, + PT_OPERATOR_BITWISE_XOR, + PT_OPERATOR_SHIFT_LEFT, + PT_OPERATOR_SHIFT_RIGHT, + PT_OPERATOR_CONCATENATE, + PT_OPERATOR_ARRAY_UNION, +}; +/* OperatorTypeHelper::add() / subtract() / ... / concat() / arrayUnion() + * ($left, $right, $phpVersion) and ::negate($type, $phpVersion) (all + * borrowed); UNDEF = pending exception */ +zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion); +zv::Val pt_operator_type_helper_negate(zval *type, zval *phpVersion); + +/* }}} */ + /* {{{ InitializerExprContext.cpp — the context a constant expression is * priced in */ diff --git a/turbo-ext/tests/initializer-escape.php b/turbo-ext/tests/initializer-escape.php index 9e48cfd9499..e0366bcb3ba 100644 --- a/turbo-ext/tests/initializer-escape.php +++ b/turbo-ext/tests/initializer-escape.php @@ -109,10 +109,11 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC // the twin resolver computes `$a - 3` over the native int and its own // constant 3: the native int is not a supertype of the twin's 3, so // their union mixes both families, which the prefix does not let - // through either - the type callback was kept before that + // through either (the native int's arithmeticSubtract() meets the twin's + // 3 in OperatorTypeHelper) - the type callback was kept before that check( ($label === 'ClassConstFetchHandler' && str_contains($e->getMessage(), 'PHPStanTurbo\\Dependencies')) - || ($label === 'BinaryOpHandler' && str_contains($e->getMessage(), 'PHPStanTurbo\\TrinaryLogic returned')), + || ($label === 'BinaryOpHandler' && (str_contains($e->getMessage(), 'PHPStanTurbo\\TrinaryLogic returned') || str_contains($e->getMessage(), 'PHPStanTurbo\\VerbosityLevel given'))), "escaping getTypeCallback ($label): " . get_class($e) . ': ' . $e->getMessage(), ); } catch (\Throwable $e) { diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index db61783b772..411d710f42d 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -1117,6 +1117,10 @@ public function enterNode(\PhpParser\Node $node) $covered[\PHPStan\Reflection\TrivialParametersAcceptor::class] = true; $covered[\PHPStan\Reflection\Assertions::class] = true; $covered[\PHPStan\Reflection\InitializerExprTypeResolver::class] = true; +$covered[\PHPStan\Type\OperatorTypeHelper::class] = true; +// OperatorTypeTrait::exponentiate() hands arithmeticPower() the newest PhpVersion; the +// native registrar spells PhpVersionFactory::MAX_PHP_VERSION as a define +check(preg_match('~#define PT_PHP_VERSION_FACTORY_MAX_PHP_VERSION (\\d+)~', (string) file_get_contents(__DIR__ . '/../src/support.h'), $maxPhpVersionMatch) === 1 && (int) $maxPhpVersionMatch[1] === \PHPStan\Php\PhpVersionFactory::MAX_PHP_VERSION, 'PT_PHP_VERSION_FACTORY_MAX_PHP_VERSION matches PhpVersionFactory::MAX_PHP_VERSION'); $covered[\PHPStan\Analyser\Generics\TemplateArgumentConstraints::class] = true; $covered[\PHPStan\Analyser\Generics\TemplateArgumentObserver::class] = true; $covered[\PHPStan\Analyser\Generics\TemplateArgumentResolver::class] = true; @@ -1186,7 +1190,7 @@ function observeTypeFamily(string $mode): array $typeFamilyPhp = observeTypeFamily('php'); $typeFamilyNative = observeTypeFamily('native'); -foreach ([\PHPStan\Type\BooleanType::class, \PHPStan\Type\Constant\ConstantBooleanType::class, \PHPStan\Type\IntegerType::class, \PHPStan\Type\Constant\ConstantIntegerType::class, \PHPStan\Type\IntegerRangeType::class, \PHPStan\Type\StringType::class, \PHPStan\Type\Constant\ConstantStringType::class, \PHPStan\Type\ClassStringType::class, \PHPStan\Type\Generic\GenericClassStringType::class, \PHPStan\Type\FloatType::class, \PHPStan\Type\Constant\ConstantFloatType::class, \PHPStan\Type\NullType::class, \PHPStan\Type\VoidType::class, \PHPStan\Type\NeverType::class, \PHPStan\Type\MixedType::class, \PHPStan\Type\StrictMixedType::class, \PHPStan\Type\ObjectWithoutClassType::class, \PHPStan\Type\StaticType::class, \PHPStan\Type\ThisType::class, \PHPStan\Type\Generic\GenericStaticType::class, \PHPStan\Type\ObjectShapeType::class, \PHPStan\Type\NonexistentParentClassType::class, \PHPStan\Type\ArrayType::class, \PHPStan\Type\Accessory\NonEmptyArrayType::class, \PHPStan\Type\Accessory\AccessoryArrayListType::class, \PHPStan\Type\Accessory\OversizedArrayType::class, \PHPStan\Type\Accessory\HasOffsetType::class, \PHPStan\Type\Accessory\HasOffsetValueType::class, \PHPStan\Type\Accessory\AccessoryNumericStringType::class, \PHPStan\Type\Accessory\AccessoryNonEmptyStringType::class, \PHPStan\Type\Accessory\AccessoryNonFalsyStringType::class, \PHPStan\Type\Accessory\AccessoryLiteralStringType::class, \PHPStan\Type\Accessory\AccessoryLowercaseStringType::class, \PHPStan\Type\Accessory\AccessoryUppercaseStringType::class, \PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType::class, \PHPStan\Type\Accessory\HasMethodType::class, \PHPStan\Type\Accessory\HasPropertyType::class, \PHPStan\Type\ObjectType::class, \PHPStan\Type\Generic\GenericObjectType::class, \PHPStan\Type\Enum\EnumCaseObjectType::class, \PHPStan\Type\IterableType::class, \PHPStan\Type\CallableType::class, \PHPStan\Type\ClosureType::class, \PHPStan\Type\Constant\ConstantArrayType::class, \PHPStan\Type\UnionType::class, \PHPStan\Type\BenevolentUnionType::class, \PHPStan\Type\IntersectionType::class, \PHPStan\Type\ErrorType::class, \PHPStan\Type\CircularTypeAliasErrorType::class, \PHPStan\Type\Generic\AbsorbedTemplateArgumentType::class, \PHPStan\Type\NonAcceptingNeverType::class, \PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType::class, \PHPStan\Type\StringNeverAcceptingObjectWithToStringType::class, \PHPStan\Type\ResourceType::class, \PHPStan\Type\TypeUtils::class, \PHPStan\Type\TypehintHelper::class, \PHPStan\Type\TypeCombinator::class, \PHPStan\Type\Generic\TemplateTypeVariance::class, \PHPStan\Type\Generic\TemplateTypeVarianceMap::class, \PHPStan\Type\Generic\TemplateTypeMap::class, \PHPStan\Type\Generic\TemplateTypeScope::class, \PHPStan\Type\Generic\TemplateTypeReference::class, \PHPStan\Type\Generic\TemplateTypeHelper::class, \PHPStan\Type\KeyOfType::class, \PHPStan\Type\ValueOfType::class, \PHPStan\Type\OffsetAccessType::class, \PHPStan\Type\ClassConstantAccessType::class, \PHPStan\Type\NewObjectType::class, \PHPStan\Type\ConditionalType::class, \PHPStan\Type\ConditionalTypeForParameter::class, \PHPStan\Type\LateResolvableArrayShapeType::class, \PHPStan\Type\Generic\UnresolvedTemplateArgumentType::class, \PHPStan\Type\Generic\TemplateArrayType::class, \PHPStan\Type\Generic\TemplateBenevolentUnionType::class, \PHPStan\Type\Generic\TemplateBooleanType::class, \PHPStan\Type\Generic\TemplateConstantArrayType::class, \PHPStan\Type\Generic\TemplateConstantIntegerType::class, \PHPStan\Type\Generic\TemplateConstantStringType::class, \PHPStan\Type\Generic\TemplateFloatType::class, \PHPStan\Type\Generic\TemplateGenericObjectType::class, \PHPStan\Type\Generic\TemplateIntegerType::class, \PHPStan\Type\Generic\TemplateIntersectionType::class, \PHPStan\Type\Generic\TemplateIterableType::class, \PHPStan\Type\Generic\TemplateMixedType::class, \PHPStan\Type\Generic\TemplateNullType::class, \PHPStan\Type\Generic\TemplateObjectShapeType::class, \PHPStan\Type\Generic\TemplateObjectType::class, \PHPStan\Type\Generic\TemplateObjectWithoutClassType::class, \PHPStan\Type\Generic\TemplateStrictMixedType::class, \PHPStan\Type\Generic\TemplateStringType::class, \PHPStan\Type\Generic\TemplateUnionType::class, \PHPStan\Type\Generic\TemplateTypeArgumentStrategy::class, \PHPStan\Type\Generic\TemplateTypeParameterStrategy::class, \PHPStan\Type\Generic\TemplateTypeFactory::class, \PHPStan\Type\Generic\TypeProjectionHelper::class, \PHPStan\Type\Constant\ConstantArrayTypeBuilder::class, \PHPStan\Type\UnionTypeHelper::class, \PHPStan\Type\CallableTypeHelper::class, \PHPStan\Type\Helper\GetTemplateTypeType::class, \PHPStan\Type\Generic\TemplateKeyOfType::class, \PHPStan\Rules\PhpDoc\UnresolvableTypeHelper::class, \PHPStan\Reflection\Native\NativeParameterReflection::class, \PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection::class, \PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection::class, \PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection::class, \PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection::class, \PHPStan\Reflection\ResolvedMethodReflection::class, \PHPStan\Reflection\Dummy\ChangedTypeMethodReflection::class, \PHPStan\Reflection\Callables\SimpleImpurePoint::class, \PHPStan\Reflection\PassedByReference::class, \PHPStan\Reflection\Php\DummyParameter::class, \PHPStan\Reflection\Php\ExtendedDummyParameter::class, \PHPStan\Analyser\ArgumentsNormalizer::class, \PHPStan\Reflection\ParametersAcceptorSelector::class, \PHPStan\Reflection\Php\PhpParameterReflection::class, \PHPStan\Reflection\Native\ExtendedNativeParameterReflection::class, \PHPStan\Reflection\FunctionVariant::class, \PHPStan\Reflection\ExtendedFunctionVariant::class, \PHPStan\Reflection\ExtendedCallableFunctionVariant::class, \PHPStan\Reflection\ResolvedFunctionVariantWithOriginal::class, \PHPStan\Reflection\TrivialParametersAcceptor::class, \PHPStan\Reflection\Assertions::class, \PHPStan\Reflection\InitializerExprTypeResolver::class, \PHPStan\Analyser\Generics\TemplateArgumentConstraints::class, \PHPStan\Analyser\Generics\TemplateArgumentObserver::class, \PHPStan\Analyser\Generics\TemplateArgumentResolver::class, \PHPStan\Analyser\RicherScopeGetTypeHelper::class, \PHPStan\Analyser\NullsafeOperatorHelper::class, \PHPStan\Analyser\LoopWrittenVariableNames::class, \PHPStan\Reflection\Php\PhpPropertyReflection::class, \PHPStan\Reflection\Dummy\ChangedTypePropertyReflection::class, \PHPStan\Reflection\ResolvedPropertyReflection::class] as $typeClass) { +foreach ([\PHPStan\Type\BooleanType::class, \PHPStan\Type\Constant\ConstantBooleanType::class, \PHPStan\Type\IntegerType::class, \PHPStan\Type\Constant\ConstantIntegerType::class, \PHPStan\Type\IntegerRangeType::class, \PHPStan\Type\StringType::class, \PHPStan\Type\Constant\ConstantStringType::class, \PHPStan\Type\ClassStringType::class, \PHPStan\Type\Generic\GenericClassStringType::class, \PHPStan\Type\FloatType::class, \PHPStan\Type\Constant\ConstantFloatType::class, \PHPStan\Type\NullType::class, \PHPStan\Type\VoidType::class, \PHPStan\Type\NeverType::class, \PHPStan\Type\MixedType::class, \PHPStan\Type\StrictMixedType::class, \PHPStan\Type\ObjectWithoutClassType::class, \PHPStan\Type\StaticType::class, \PHPStan\Type\ThisType::class, \PHPStan\Type\Generic\GenericStaticType::class, \PHPStan\Type\ObjectShapeType::class, \PHPStan\Type\NonexistentParentClassType::class, \PHPStan\Type\ArrayType::class, \PHPStan\Type\Accessory\NonEmptyArrayType::class, \PHPStan\Type\Accessory\AccessoryArrayListType::class, \PHPStan\Type\Accessory\OversizedArrayType::class, \PHPStan\Type\Accessory\HasOffsetType::class, \PHPStan\Type\Accessory\HasOffsetValueType::class, \PHPStan\Type\Accessory\AccessoryNumericStringType::class, \PHPStan\Type\Accessory\AccessoryNonEmptyStringType::class, \PHPStan\Type\Accessory\AccessoryNonFalsyStringType::class, \PHPStan\Type\Accessory\AccessoryLiteralStringType::class, \PHPStan\Type\Accessory\AccessoryLowercaseStringType::class, \PHPStan\Type\Accessory\AccessoryUppercaseStringType::class, \PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType::class, \PHPStan\Type\Accessory\HasMethodType::class, \PHPStan\Type\Accessory\HasPropertyType::class, \PHPStan\Type\ObjectType::class, \PHPStan\Type\Generic\GenericObjectType::class, \PHPStan\Type\Enum\EnumCaseObjectType::class, \PHPStan\Type\IterableType::class, \PHPStan\Type\CallableType::class, \PHPStan\Type\ClosureType::class, \PHPStan\Type\Constant\ConstantArrayType::class, \PHPStan\Type\UnionType::class, \PHPStan\Type\BenevolentUnionType::class, \PHPStan\Type\IntersectionType::class, \PHPStan\Type\ErrorType::class, \PHPStan\Type\CircularTypeAliasErrorType::class, \PHPStan\Type\Generic\AbsorbedTemplateArgumentType::class, \PHPStan\Type\NonAcceptingNeverType::class, \PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType::class, \PHPStan\Type\StringNeverAcceptingObjectWithToStringType::class, \PHPStan\Type\ResourceType::class, \PHPStan\Type\TypeUtils::class, \PHPStan\Type\TypehintHelper::class, \PHPStan\Type\TypeCombinator::class, \PHPStan\Type\Generic\TemplateTypeVariance::class, \PHPStan\Type\Generic\TemplateTypeVarianceMap::class, \PHPStan\Type\Generic\TemplateTypeMap::class, \PHPStan\Type\Generic\TemplateTypeScope::class, \PHPStan\Type\Generic\TemplateTypeReference::class, \PHPStan\Type\Generic\TemplateTypeHelper::class, \PHPStan\Type\KeyOfType::class, \PHPStan\Type\ValueOfType::class, \PHPStan\Type\OffsetAccessType::class, \PHPStan\Type\ClassConstantAccessType::class, \PHPStan\Type\NewObjectType::class, \PHPStan\Type\ConditionalType::class, \PHPStan\Type\ConditionalTypeForParameter::class, \PHPStan\Type\LateResolvableArrayShapeType::class, \PHPStan\Type\Generic\UnresolvedTemplateArgumentType::class, \PHPStan\Type\Generic\TemplateArrayType::class, \PHPStan\Type\Generic\TemplateBenevolentUnionType::class, \PHPStan\Type\Generic\TemplateBooleanType::class, \PHPStan\Type\Generic\TemplateConstantArrayType::class, \PHPStan\Type\Generic\TemplateConstantIntegerType::class, \PHPStan\Type\Generic\TemplateConstantStringType::class, \PHPStan\Type\Generic\TemplateFloatType::class, \PHPStan\Type\Generic\TemplateGenericObjectType::class, \PHPStan\Type\Generic\TemplateIntegerType::class, \PHPStan\Type\Generic\TemplateIntersectionType::class, \PHPStan\Type\Generic\TemplateIterableType::class, \PHPStan\Type\Generic\TemplateMixedType::class, \PHPStan\Type\Generic\TemplateNullType::class, \PHPStan\Type\Generic\TemplateObjectShapeType::class, \PHPStan\Type\Generic\TemplateObjectType::class, \PHPStan\Type\Generic\TemplateObjectWithoutClassType::class, \PHPStan\Type\Generic\TemplateStrictMixedType::class, \PHPStan\Type\Generic\TemplateStringType::class, \PHPStan\Type\Generic\TemplateUnionType::class, \PHPStan\Type\Generic\TemplateTypeArgumentStrategy::class, \PHPStan\Type\Generic\TemplateTypeParameterStrategy::class, \PHPStan\Type\Generic\TemplateTypeFactory::class, \PHPStan\Type\Generic\TypeProjectionHelper::class, \PHPStan\Type\Constant\ConstantArrayTypeBuilder::class, \PHPStan\Type\UnionTypeHelper::class, \PHPStan\Type\CallableTypeHelper::class, \PHPStan\Type\Helper\GetTemplateTypeType::class, \PHPStan\Type\Generic\TemplateKeyOfType::class, \PHPStan\Rules\PhpDoc\UnresolvableTypeHelper::class, \PHPStan\Reflection\Native\NativeParameterReflection::class, \PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection::class, \PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection::class, \PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection::class, \PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection::class, \PHPStan\Reflection\ResolvedMethodReflection::class, \PHPStan\Reflection\Dummy\ChangedTypeMethodReflection::class, \PHPStan\Reflection\Callables\SimpleImpurePoint::class, \PHPStan\Reflection\PassedByReference::class, \PHPStan\Reflection\Php\DummyParameter::class, \PHPStan\Reflection\Php\ExtendedDummyParameter::class, \PHPStan\Analyser\ArgumentsNormalizer::class, \PHPStan\Reflection\ParametersAcceptorSelector::class, \PHPStan\Reflection\Php\PhpParameterReflection::class, \PHPStan\Reflection\Native\ExtendedNativeParameterReflection::class, \PHPStan\Reflection\FunctionVariant::class, \PHPStan\Reflection\ExtendedFunctionVariant::class, \PHPStan\Reflection\ExtendedCallableFunctionVariant::class, \PHPStan\Reflection\ResolvedFunctionVariantWithOriginal::class, \PHPStan\Reflection\TrivialParametersAcceptor::class, \PHPStan\Reflection\Assertions::class, \PHPStan\Reflection\InitializerExprTypeResolver::class, \PHPStan\Type\OperatorTypeHelper::class, \PHPStan\Analyser\Generics\TemplateArgumentConstraints::class, \PHPStan\Analyser\Generics\TemplateArgumentObserver::class, \PHPStan\Analyser\Generics\TemplateArgumentResolver::class, \PHPStan\Analyser\RicherScopeGetTypeHelper::class, \PHPStan\Analyser\NullsafeOperatorHelper::class, \PHPStan\Analyser\LoopWrittenVariableNames::class, \PHPStan\Reflection\Php\PhpPropertyReflection::class, \PHPStan\Reflection\Dummy\ChangedTypePropertyReflection::class, \PHPStan\Reflection\ResolvedPropertyReflection::class] as $typeClass) { check(($typeFamilyPhp["native $typeClass"] ?? null) === false, "type-family.php php: $typeClass is the PHP twin"); check(($typeFamilyNative["native $typeClass"] ?? null) === true, "type-family.php native: $typeClass is the native class"); unset($typeFamilyPhp["native $typeClass"], $typeFamilyNative["native $typeClass"]); diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index 04bf5951fa4..da8eb4f6632 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7670,6 +7670,7 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu require_once $initializerFixture; } $observations['native PHPStan\Reflection\InitializerExprTypeResolver'] = (new ReflectionMethod(\PHPStan\Reflection\InitializerExprTypeResolver::class, 'getType'))->isInternal(); +$observations['native PHPStan\Type\OperatorTypeHelper'] = (new ReflectionMethod(\PHPStan\Type\OperatorTypeHelper::class, 'add'))->isInternal(); { $resolver = $stringContainer->getByType(\PHPStan\Reflection\InitializerExprTypeResolver::class); $r = []; @@ -7774,7 +7775,6 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu $r["getUnaryMinusType $name"] = $catching(static fn () => $resolver->getUnaryMinusType($te($operand), $getType)); $r["getUnaryPlusType $name"] = $catching(static fn () => $resolver->getUnaryPlusType($te($operand), $getType)); $r["getBitwiseNotType $name"] = $catching(static fn () => $resolver->getBitwiseNotType($te($operand), $getType)); - $r["getUnaryMinusTypeFromType $name"] = $catching(static fn () => $resolver->getUnaryMinusTypeFromType($te($operand), $operand)); $r["getBitwiseNotTypeFromType $name"] = $catching(static fn () => $resolver->getBitwiseNotTypeFromType($operand)); $r["getCastObjectType $name"] = $catching(static fn () => $resolver->getCastObjectType($operand)); foreach ($castClasses as $castName => $castClass) { @@ -7783,43 +7783,44 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu } $r['getCastObjectType union of shapes'] = $catching(static fn () => $resolver->getCastObjectType(new \PHPStan\Type\UnionType([$operands['constArray'], $operands['constShape'], new \PHPStan\Type\ObjectType(\stdClass::class)]))); - // the private helpers, called in the class's scope + // the private helpers of OperatorTypeHelper (the operators' arithmetic + // moved there from the resolver), called in the class's scope + $staticPrivate = static fn (string $method, mixed ...$args): mixed => \Closure::bind(static fn () => self::$method(...$args), null, \PHPStan\Type\OperatorTypeHelper::class)(); $private = static fn (string $method, mixed ...$args): mixed => (fn () => $this->$method(...$args))->call($resolver); - $staticPrivate = static fn (string $method, mixed ...$args): mixed => \Closure::bind(static fn () => self::$method(...$args), null, \PHPStan\Reflection\InitializerExprTypeResolver::class)(); $nodes = [ - 'plus' => new \PhpParser\Node\Expr\BinaryOp\Plus($te($int(1)), $te($int(1))), - 'minus' => new \PhpParser\Node\Expr\BinaryOp\Minus($te($int(1)), $te($int(1))), - 'mul' => new \PhpParser\Node\Expr\BinaryOp\Mul($te($int(1)), $te($int(1))), - 'div' => new \PhpParser\Node\Expr\BinaryOp\Div($te($int(1)), $te($int(1))), - 'shiftLeft' => new \PhpParser\Node\Expr\BinaryOp\ShiftLeft($te($int(1)), $te($int(1))), - 'shiftRight' => new \PhpParser\Node\Expr\BinaryOp\ShiftRight($te($int(1)), $te($int(1))), - 'mod' => new \PhpParser\Node\Expr\BinaryOp\Mod($te($int(1)), $te($int(1))), + 'plus' => '+', + 'minus' => '-', + 'mul' => '*', + 'div' => '/', + 'shiftLeft' => '<<', + 'shiftRight' => '>>', + 'mod' => '%', ]; $integerOperands = ['int0' => $int(0), 'int7' => $int(7), 'int-3' => $int(-3), 'intMax' => $int(PHP_INT_MAX), 'intMin' => $int(PHP_INT_MIN), 'range0-10' => $range(0, 10), 'rangeMin--1' => $range(null, -1), 'range5-max' => $range(5, null), 'range-4-6' => $range(-4, 6), 'rangeMin-max' => $range(-1000, null), 'unionRangeConst' => $operands['unionRangeConst'], 'benevolentRanges' => new \PHPStan\Type\BenevolentUnionType([$range(0, 3), $int(-8)])]; foreach ($nodes as $nodeName => $node) { foreach ($integerOperands as $leftName => $left) { foreach ($integerOperands as $rightName => $right) { - $r["integerRangeMath $nodeName $leftName $rightName"] = $catching(static fn () => $left instanceof \PHPStan\Type\UnionType ? 'skip' : $private('integerRangeMath', $left, $node, $right)); - $r["resolveCommonMath $nodeName $leftName $rightName"] = $catching(static fn () => $private('resolveCommonMath', $node, $left, $right)); + $r["integerRangeMath $nodeName $leftName $rightName"] = $catching(static fn () => $left instanceof \PHPStan\Type\UnionType ? 'skip' : $staticPrivate('integerRangeMath', $left, $node, $right)); + $r["resolveCommonMath $nodeName $leftName $rightName"] = $catching(static fn () => $staticPrivate('resolveCommonMath', $node, $left, $right)); } } } foreach ($operands as $name => $operand) { - $r["optimizeScalarType $name"] = $catching(static fn () => $private('optimizeScalarType', $operand)); - $r["getNonNegativeIntegerBounds $name"] = $catching(static fn () => $private('getNonNegativeIntegerBounds', $operand)); - $r["computeBitwiseAndRange $name range0-10"] = $catching(static fn () => $private('computeBitwiseAndRange', $operand, $range(0, 10))); - $r["computeBitwiseOrXorRange $name int7"] = $catching(static fn () => $private('computeBitwiseOrXorRange', $operand, $int(7))); - $r["getNeverType $name never"] = $catching(static fn () => $private('getNeverType', $operand, $operands['neverExplicit'])); - $r["getFiniteOrConstantScalarTypes $name unionConsts"] = $catching(static fn () => $private('getFiniteOrConstantScalarTypes', $operand, $operands['unionConsts'], static fn ($a, $b) => $a | $b)); + $r["optimizeScalarType $name"] = $catching(static fn () => $staticPrivate('optimizeScalarType', $operand)); + $r["getNonNegativeIntegerBounds $name"] = $catching(static fn () => $staticPrivate('getNonNegativeIntegerBounds', $operand)); + $r["computeBitwiseAndRange $name range0-10"] = $catching(static fn () => $staticPrivate('computeBitwiseAndRange', $operand, $range(0, 10))); + $r["computeBitwiseOrXorRange $name int7"] = $catching(static fn () => $staticPrivate('computeBitwiseOrXorRange', $operand, $int(7))); + $r["getNeverType $name never"] = $catching(static fn () => $staticPrivate('getNeverType', $operand, $operands['neverExplicit'])); + $r["getFiniteOrConstantScalarTypes $name unionConsts"] = $catching(static fn () => $staticPrivate('getFiniteOrConstantScalarTypes', $operand, $operands['unionConsts'], static fn ($a, $b) => $a | $b)); } foreach ([0, 1, 5, 200, 1000, PHP_INT_MAX] as $value) { $r["allBitsMask $value"] = $catching(static fn () => $staticPrivate('allBitsMask', $value)); } foreach ($operands as $name => $operand) { - $r["getIntegerBounds $name"] = $catching(static fn () => $private('getIntegerBounds', $operand)); + $r["getIntegerBounds $name"] = $catching(static fn () => $staticPrivate('getIntegerBounds', $operand)); } foreach ($integerOperands as $name => $operand) { - $r["getIntegerBounds $name"] = $catching(static fn () => $private('getIntegerBounds', $operand)); + $r["getIntegerBounds $name"] = $catching(static fn () => $staticPrivate('getIntegerBounds', $operand)); } foreach ([[null, 5], [-7, 3], [PHP_INT_MIN, 1], [PHP_INT_MIN + 1, 1], [0, 0], [3, null], [-2, -9]] as [$divisorMin, $divisorMax]) { $r['getMaxModuloMagnitude ' . var_export($divisorMin, true) . ' ' . var_export($divisorMax, true)] = $catching(static fn () => $staticPrivate('getMaxModuloMagnitude', $divisorMin, $divisorMax)); @@ -7831,7 +7832,24 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu $r["toIntBound $i"] = $catching(static fn () => $staticPrivate('toIntBound', $value)); } foreach ([null, true, 1, 1.5, 'x'] as $i => $value) { - $r["getTypeFromValue $i"] = $catching(static fn () => $private('getTypeFromValue', $value)); + $r["getTypeFromValue $i"] = $catching(static fn () => $staticPrivate('getTypeFromValue', $value)); + } + // the operator methods of OperatorTypeTrait (their bodies are OperatorTypeHelper's) + // over every pair of operands, at two PHP versions + $operatorMethods = ['arithmeticAdd', 'arithmeticSubtract', 'arithmeticMultiply', 'arithmeticDivide', 'arithmeticIntDivide', 'arithmeticModulo', 'arithmeticPower', 'bitwiseAnd', 'bitwiseOr', 'bitwiseXor', 'bitwiseShiftLeft', 'bitwiseShiftRight', 'stringConcatenate', 'arrayUnion']; + foreach ([70400, 80500] as $operatorVersionId) { + $operatorVersion = new \PHPStan\Php\PhpVersion($operatorVersionId); + foreach ($operands + $integerOperands as $leftName => $left) { + $r["arithmeticNegate $operatorVersionId $leftName"] = $catching(static fn () => $left->arithmeticNegate($operatorVersion)); + foreach ($operands + $integerOperands as $rightName => $right) { + foreach ($operatorMethods as $operatorMethod) { + $r["$operatorMethod $operatorVersionId $leftName $rightName"] = $catching(static fn () => $left->$operatorMethod($right, $operatorVersion)); + } + } + } + } + foreach ($operands as $name => $operand) { + $r["exponentiate $name int7"] = $catching(static fn () => $operand->exponentiate($int(7))); } $r['resolveConstantArrayTypeComparison callback'] = $catching(static fn () => $viewResult($private('resolveConstantArrayTypeComparison', $operands['constArray'], $operands['constArray'], static fn ($a, $b) => new \PHPStan\Type\TypeResult(new \PHPStan\Type\BooleanType(), ['reason'])))); $shapes = [ From a6667b282fd4b7a8fccc115b831fdf05b1d58077 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 18:01:22 +0000 Subject: [PATCH 03/14] Saturate numeric strings that become integer operands %, <<, >>, &, | and ^ converted a constant numeric string through the float of toNumber(), so '9223372036854775808' wrapped around instead of saturating like PHP's (int) conversion does, and a shift by it crashed the analysis with ArithmeticError. The operands now come from toInteger(). Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 31 +++++++--- .../nsrt/integer-operand-numeric-string.php | 32 +++++++++++ turbo-ext/src/OperatorTypeHelper.cpp | 54 +++++++++++++----- turbo-ext/src/generated/OperatorTypeHelper.h | 57 ++++++++++--------- turbo-ext/tests/type-family.php | 10 +++- 5 files changed, 136 insertions(+), 48 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/integer-operand-numeric-string.php diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 8bc0fb7309e..4de2d69ecad 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -357,7 +357,7 @@ private static function getFiniteOrConstantScalarTypes(Type $leftType, Type $rig throw new ShouldNotHappenException(); } - $resultValue = $operationCallable($leftNumberType->getValue(), $rightNumberType->getValue()); + $resultValue = $operationCallable(self::toIntegerOperand($leftTypeInner), self::toIntegerOperand($rightTypeInner)); $resultType = self::getTypeFromValue($resultValue); } $resultTypes[] = $resultType; @@ -464,12 +464,12 @@ public static function modulo(Type $leftType, Type $rightType, PhpVersion $phpVe throw new ShouldNotHappenException(); } - $rightIntegerValue = (int) $rightNumberType->getValue(); + $rightIntegerValue = self::toIntegerOperand($rightTypeInner); if ($rightIntegerValue === 0) { return new ErrorType(); } - $resultType = self::getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue); + $resultType = self::getTypeFromValue(self::toIntegerOperand($leftTypeInner) % $rightIntegerValue); $resultTypes[] = $resultType; } } @@ -965,11 +965,12 @@ public static function shiftLeft(Type $leftType, Type $rightType, PhpVersion $ph throw new ShouldNotHappenException(); } - if ($rightNumberType->getValue() < 0) { + $rightIntegerValue = self::toIntegerOperand($rightTypeInner); + if ($rightIntegerValue < 0) { return new ErrorType(); } - $resultType = self::getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue())); + $resultType = self::getTypeFromValue(self::toIntegerOperand($leftTypeInner) << $rightIntegerValue); $resultTypes[] = $resultType; } } @@ -1018,11 +1019,12 @@ public static function shiftRight(Type $leftType, Type $rightType, PhpVersion $p throw new ShouldNotHappenException(); } - if ($rightNumberType->getValue() < 0) { + $rightIntegerValue = self::toIntegerOperand($rightTypeInner); + if ($rightIntegerValue < 0) { return new ErrorType(); } - $resultType = self::getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue())); + $resultType = self::getTypeFromValue(self::toIntegerOperand($leftTypeInner) >> $rightIntegerValue); $resultTypes[] = $resultType; } } @@ -1559,6 +1561,21 @@ private static function toIntBound(float $value): ?int 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 + * wrapping around like the float toNumber() gives. + */ + private static function toIntegerOperand(ConstantScalarType $type): int + { + $values = $type->toInteger()->getConstantScalarValues(); + if (count($values) !== 1 || !is_int($values[0])) { + throw new ShouldNotHappenException(); + } + + return $values[0]; + } + /** * @param mixed $value */ diff --git a/tests/PHPStan/Analyser/nsrt/integer-operand-numeric-string.php b/tests/PHPStan/Analyser/nsrt/integer-operand-numeric-string.php new file mode 100644 index 00000000000..1054e927818 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/integer-operand-numeric-string.php @@ -0,0 +1,32 @@ +> '9223372036854775808'); + assertType('-2', '9223372036854775808' << 1); + assertType('4611686018427387903', '9223372036854775808' >> 1); + assertType('1', '9223372036854775808' % 3); + assertType('0', 9223372036854775807 % '9223372036854775808'); +} + +/** + * @param int<0, 3> $range + */ +function doBar(int $range): void +{ + assertType('0|1|2|3', '9223372036854775808' & $range); + assertType('9223372036854775807', $range | '9223372036854775808'); +} diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 2f3c5333706..93cf41c8ce0 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -357,11 +357,9 @@ class OperatorTypeHelper return -1; } - zv::Val leftValue = callOn(leftNumberType.raw(), mGetValue); - if (UNEXPECTED(leftValue.isUndef())) return -1; - zv::Val rightValue = callOn(rightNumberType.raw(), mGetValue); - if (UNEXPECTED(rightValue.isUndef())) return -1; - zv::Val resultValue = applyOperation(operation, operationCallable, leftValue.raw(), rightValue.raw()); + zval leftInteger, rightInteger; + if (UNEXPECTED(!toIntegerOperand(leftTypeInner, leftInteger) || !toIntegerOperand(rightTypeInner, rightInteger))) return -1; + zv::Val resultValue = applyOperation(operation, operationCallable, &leftInteger, &rightInteger); if (UNEXPECTED(resultValue.isUndef())) return -1; resultType = typeFromValue(resultValue.raw()); } @@ -373,6 +371,23 @@ class OperatorTypeHelper return result.isUndef() ? -1 : 0; } + /* Mirrors toIntegerOperand(): the only value of + * $type->toInteger()->getConstantScalarValues(), as an IS_LONG zval */ + static bool toIntegerOperand(zval *type, zval &integer) + { + zv::Val integerType = callOn(type, mToInteger); + if (UNEXPECTED(integerType.isUndef())) return false; + zv::Val values = callOn(integerType.raw(), mGetConstantScalarValues); + if (UNEXPECTED(values.isUndef()) || UNEXPECTED(!requireArray(values.raw()))) return false; + zval *value = zend_hash_index_find(Z_ARRVAL_P(values.raw()), 0); + if (zend_hash_num_elements(Z_ARRVAL_P(values.raw())) != 1 || value == NULL || Z_TYPE_P(value) != IS_LONG) { + pt_throw_should_not_happen(); + return false; + } + ZVAL_LONG(&integer, Z_LVAL_P(value)); + return true; + } + /* $operationCallable($a, $b) or the operator */ static zv::Val applyOperation(pt_ietr_binary_op operation, zval *operationCallable, zval *a, zval *b) { @@ -451,27 +466,26 @@ class OperatorTypeHelper break; case IETR_MOD: { zval rightInteger; - ZVAL_LONG(&rightInteger, zval_get_long(rightValue.raw())); + if (UNEXPECTED(!toIntegerOperand(rightEntry.value().raw(), rightInteger))) return -1; if (Z_LVAL(rightInteger) == 0) { result = newErrorType(); return result.isUndef() ? -1 : 0; } zval leftInteger; - ZVAL_LONG(&leftInteger, zval_get_long(leftValue.raw())); + if (UNEXPECTED(!toIntegerOperand(leftEntry.value().raw(), leftInteger))) return -1; resultValue = phpOp(mod_function, &leftInteger, &rightInteger); break; } case IETR_SHIFT_LEFT: case IETR_SHIFT_RIGHT: { - zval zero; - ZVAL_LONG(&zero, 0); - if (lessThan(rightValue.raw(), &zero)) { + zval rightInteger; + if (UNEXPECTED(!toIntegerOperand(rightEntry.value().raw(), rightInteger))) return -1; + if (Z_LVAL(rightInteger) < 0) { result = newErrorType(); return result.isUndef() ? -1 : 0; } - zval leftInteger, rightInteger; - ZVAL_LONG(&leftInteger, zval_get_long(leftValue.raw())); - ZVAL_LONG(&rightInteger, zval_get_long(rightValue.raw())); + zval leftInteger; + if (UNEXPECTED(!toIntegerOperand(leftEntry.value().raw(), leftInteger))) return -1; resultValue = phpOp(kind == IETR_SHIFT_LEFT ? shift_left_function : shift_right_function, &leftInteger, &rightInteger); break; } @@ -2323,6 +2337,20 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) cls.method(sigs::getNeverType, OT_TYPES_GLUE(OperatorTypeHelper::getNeverType(leftType, rightType))); + cls.method(sigs::toIntegerOperand, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *type; + if (!zp::parse(execute_data, type)) RETURN_THROWS(); + zend_class_entry *ce = pt_class(PT_CLASS_CONSTANT_SCALAR_TYPE); + if (UNEXPECTED(ce == NULL)) RETURN_THROWS(); + if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(type), ce))) { + zend_argument_type_error(1, "must be of type PHPStan\\Type\\ConstantScalarType, %s given", zend_zval_value_name(type)); + RETURN_THROWS(); + } + zval integer; + if (UNEXPECTED(!OperatorTypeHelper::toIntegerOperand(type, integer))) RETURN_THROWS(); + RETURN_LONG(Z_LVAL(integer)); + }); + cls.method(sigs::negate, [](INTERNAL_FUNCTION_PARAMETERS) { zval *type, *phpVersion; if (!zp::parse(execute_data, type, phpVersion)) RETURN_THROWS(); diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index e7ed79e5603..764b24ba7de 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -68,19 +68,21 @@ inline constexpr char strings[] = "shiftLeftOverflows\0" /* 547 */ "negateNumber\0" /* 566 */ "toIntBound\0" /* 579 */ - "getTypeFromValue\0" /* 590 */ - "getNeverType\0" /* 607 */ - "negate\0" /* 620 */ - "numeratorType\0" /* 627 */ - "divisorType\0" /* 641 */ - "intDivide\0" /* 653 */ - "getIntervals\0" /* 663 */ - "numeratorMin\0" /* 676 */ - "numeratorMax\0" /* 689 */ - "divideIntervals\0" /* 702 */ - "numerator\0" /* 718 */ - "divisor\0" /* 728 */ - "divideBounds"; /* 736 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 590 */ + "toIntegerOperand\0" /* 622 */ + "getTypeFromValue\0" /* 639 */ + "getNeverType\0" /* 656 */ + "negate\0" /* 669 */ + "numeratorType\0" /* 676 */ + "divisorType\0" /* 690 */ + "intDivide\0" /* 702 */ + "getIntervals\0" /* 712 */ + "numeratorMin\0" /* 725 */ + "numeratorMax\0" /* 738 */ + "divideIntervals\0" /* 751 */ + "numerator\0" /* 767 */ + "divisor\0" /* 777 */ + "divideBounds"; /* 785 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* concat $left */ reg::packed(23, 0, 5), /* concat $right */ @@ -172,6 +174,8 @@ inline constexpr reg::PackedArg args[] = { reg::packed(63, 0, 5), /* negateNumber return */ reg::packed(465, MAY_BE_DOUBLE), /* toIntBound $value */ reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ + reg::packed(249, 0, 590), /* toIntegerOperand $type */ + reg::packed(63, MAY_BE_LONG), /* toIntegerOperand return */ reg::packed(465, 0), /* getTypeFromValue $value */ reg::packed(63, 0, 5), /* getTypeFromValue return */ reg::packed(71, 0, 5), /* getNeverType $leftType */ @@ -180,19 +184,19 @@ inline constexpr reg::PackedArg args[] = { reg::packed(249, 0, 5), /* negate $type */ reg::packed(29, 0, 40), /* negate $phpVersion */ reg::packed(63, 0, 5), /* negate return */ - reg::packed(627, 0, 5), /* intDivide $numeratorType */ - reg::packed(641, 0, 5), /* intDivide $divisorType */ + reg::packed(676, 0, 5), /* intDivide $numeratorType */ + reg::packed(690, 0, 5), /* intDivide $divisorType */ reg::packed(29, 0, 40), /* intDivide $phpVersion */ reg::packed(63, 0, 5), /* intDivide return */ reg::packed(249, 0, 5), /* getIntervals $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntervals return */ - reg::packed(676, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ - reg::packed(689, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ + reg::packed(725, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ + reg::packed(738, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ reg::packed(205, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ reg::packed(216, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ reg::packed(63, 0, 5), /* divideIntervals return */ - reg::packed(718, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ - reg::packed(728, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ + reg::packed(767, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ + reg::packed(777, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ reg::packed(63, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds return */ }; using Sig = reg::Sig; @@ -227,13 +231,14 @@ inline constexpr sigtab::Sig integerRangeMath = { { 524 /* integerRangeMath */, inline constexpr sigtab::Sig shiftLeftOverflows = { { 547 /* shiftLeftOverflows */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig negateNumber = { { 566 /* negateNumber */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig toIntBound = { { 579 /* toIntBound */, 1, 88, 1, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 590 /* getTypeFromValue */, 1, 90, 1, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 607 /* getNeverType */, 2, 92, 2, 94, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 620 /* negate */, 2, 95, 2, 97, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 653 /* intDivide */, 3, 98, 3, 101, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntervals = { { 663 /* getIntervals */, 1, 102, 1, 103, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntervals = { { 702 /* divideIntervals */, 4, 104, 4, 108, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBounds = { { 736 /* divideBounds */, 2, 109, 2, 111, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntegerOperand = { { 622 /* toIntegerOperand */, 1, 90, 1, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 639 /* getTypeFromValue */, 1, 92, 1, 93, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNeverType = { { 656 /* getNeverType */, 2, 94, 2, 96, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negate = { { 669 /* negate */, 2, 97, 2, 99, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 702 /* intDivide */, 3, 100, 3, 103, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntervals = { { 712 /* getIntervals */, 1, 104, 1, 105, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntervals = { { 751 /* divideIntervals */, 4, 106, 4, 110, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBounds = { { 785 /* divideBounds */, 2, 111, 2, 113, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index da8eb4f6632..8fff803e6dd 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7797,6 +7797,12 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu 'mod' => '%', ]; $integerOperands = ['int0' => $int(0), 'int7' => $int(7), 'int-3' => $int(-3), 'intMax' => $int(PHP_INT_MAX), 'intMin' => $int(PHP_INT_MIN), 'range0-10' => $range(0, 10), 'rangeMin--1' => $range(null, -1), 'range5-max' => $range(5, null), 'range-4-6' => $range(-4, 6), 'rangeMin-max' => $range(-1000, null), 'unionRangeConst' => $operands['unionRangeConst'], 'benevolentRanges' => new \PHPStan\Type\BenevolentUnionType([$range(0, 3), $int(-8)])]; + $overflowingOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809')]; + foreach ($integerOperands + $overflowingOperands as $name => $operand) { + foreach ($operand->getConstantScalarTypes() as $i => $constant) { + $r["toIntegerOperand $name $i"] = $catching(static fn () => $staticPrivate('toIntegerOperand', $constant)); + } + } foreach ($nodes as $nodeName => $node) { foreach ($integerOperands as $leftName => $left) { foreach ($integerOperands as $rightName => $right) { @@ -7839,9 +7845,9 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu $operatorMethods = ['arithmeticAdd', 'arithmeticSubtract', 'arithmeticMultiply', 'arithmeticDivide', 'arithmeticIntDivide', 'arithmeticModulo', 'arithmeticPower', 'bitwiseAnd', 'bitwiseOr', 'bitwiseXor', 'bitwiseShiftLeft', 'bitwiseShiftRight', 'stringConcatenate', 'arrayUnion']; foreach ([70400, 80500] as $operatorVersionId) { $operatorVersion = new \PHPStan\Php\PhpVersion($operatorVersionId); - foreach ($operands + $integerOperands as $leftName => $left) { + foreach ($operands + $integerOperands + $overflowingOperands as $leftName => $left) { $r["arithmeticNegate $operatorVersionId $leftName"] = $catching(static fn () => $left->arithmeticNegate($operatorVersion)); - foreach ($operands + $integerOperands as $rightName => $right) { + foreach ($operands + $integerOperands + $overflowingOperands as $rightName => $right) { foreach ($operatorMethods as $operatorMethod) { $r["$operatorMethod $operatorVersionId $leftName $rightName"] = $catching(static fn () => $left->$operatorMethod($right, $operatorVersion)); } From 1ead4e91b7dbe6acaee2f50b2be1dfc3bdbad7f2 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 18:11:12 +0000 Subject: [PATCH 04/14] Infer only integer zero products as the constant 0 Zero times a float is 0.0, -0.0 or NAN (0 * INF), so 0 * $float is float now instead of 0.0, and zero times a type that is not certainly an integer (numeric-string, int|float) is no longer the integer 0. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 15 +++----- tests/PHPStan/Analyser/nsrt/math.php | 10 ++--- .../Analyser/nsrt/multiply-by-zero.php | 24 ++++++++++++ turbo-ext/src/OperatorTypeHelper.cpp | 37 +++++++------------ 4 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/multiply-by-zero.php diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 4de2d69ecad..0b5def84fc8 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -920,18 +920,13 @@ public static function multiply(Type $leftType, Type $rightType, PhpVersion $php $rightType = self::optimizeScalarType($rightType); } + // zero times an integer is always 0; times a float it is 0.0, -0.0 or NAN $leftNumberType = $leftType->toNumber(); - if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) { - if ($rightType->isFloat()->yes()) { - return new ConstantFloatType(0.0); - } - return new ConstantIntegerType(0); - } $rightNumberType = $rightType->toNumber(); - if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) { - if ($leftType->isFloat()->yes()) { - return new ConstantFloatType(0.0); - } + if ( + ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0 && $rightNumberType->isInteger()->yes()) + || ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0 && $leftNumberType->isInteger()->yes()) + ) { return new ConstantIntegerType(0); } diff --git a/tests/PHPStan/Analyser/nsrt/math.php b/tests/PHPStan/Analyser/nsrt/math.php index 9d12809783e..99ec5fc5082 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('0.0', $f * false); + assertType('float', $f * false); assertType('0', $range * false); assertType('0', $i * '0'); - assertType('0.0', $f * '0'); + assertType('float', $f * '0'); assertType('0', $range * '0'); assertType('0', $i * 0); - assertType('0.0', $f * 0); + assertType('float', $f * 0); assertType('0', $range * 0); assertType('0', 0 * $i); - assertType('0.0', 0 * $f); + assertType('float', 0 * $f); assertType('0', 0 * $range); $i *= 0; $f *= 0; $range *= 0; assertType('0', $i); - assertType('0.0', $f); + assertType('float', $f); assertType('0', $range); } diff --git a/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php b/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php new file mode 100644 index 00000000000..3e56bd69acc --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/multiply-by-zero.php @@ -0,0 +1,24 @@ += 8.0 + +namespace MultiplyByZero; + +use function PHPStan\Testing\assertType; + +/** + * @param numeric-string $numericString + */ +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); + + // an integer times zero is always the integer 0 + assertType('0', 0 * $int); + assertType('0', $int * null); + assertType('0', $bool * 0); +} diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 93cf41c8ce0..ec4c10e3125 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -1141,29 +1141,20 @@ class OperatorTypeHelper } if (kind == IETR_MUL) { - { - IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); - int zero = isConstantIntegerZero(leftNumberType.raw()); - if (UNEXPECTED(zero < 0)) return zv::Val(); - if (zero) { - IETR_TRI(rightIsFloat, triOn(rightType, mIsFloat)); - if (rightIsFloat == PT_TRI_YES) { - return newConstantFloatType(0.0); - } - return newConstantIntegerType(0); - } - } - { - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - int zero = isConstantIntegerZero(rightNumberType.raw()); - if (UNEXPECTED(zero < 0)) return zv::Val(); - if (zero) { - IETR_TRI(leftIsFloat, triOn(leftType, mIsFloat)); - if (leftIsFloat == PT_TRI_YES) { - return newConstantFloatType(0.0); - } - return newConstantIntegerType(0); - } + /* zero times an integer is always 0; times a float it is 0.0, -0.0 or NAN */ + IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); + IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); + int leftZero = isConstantIntegerZero(leftNumberType.raw()); + if (UNEXPECTED(leftZero < 0)) return zv::Val(); + if (leftZero) { + IETR_TRI(rightIsInteger, triOn(rightNumberType.raw(), mIsInteger)); + if (rightIsInteger == PT_TRI_YES) return newConstantIntegerType(0); + } + int rightZero = isConstantIntegerZero(rightNumberType.raw()); + if (UNEXPECTED(rightZero < 0)) return zv::Val(); + if (rightZero) { + IETR_TRI(leftIsInteger, triOn(leftNumberType.raw(), mIsInteger)); + if (leftIsInteger == PT_TRI_YES) return newConstantIntegerType(0); } } From 74bad8340ace836b3db5da031ed9b5e1598b3f4f Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 18:35:01 +0000 Subject: [PATCH 05/14] Infer powers of integer intervals with non-monotonic bases The bounds of base ** exponent were the powers of the bounds, which holds only for bases of at least 1: 0 ** int<0, 10> was never instead of 0|1, (-1) ** int<0, 10> was 1 instead of -1|1, and an int<2, 3> base raised to int missed the floats of negative exponents. ExponentiateHelper now handles integer intervals for every base, IntegerRangeType inherits it from IntegerType and null is exponentiated as 0 instead of always 0|1. A float operand makes the result a float, the power of 0 is 1|1.0 unless the base is an integer, and a non-numeric constant string operand is an error. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 6 - src/Type/ExponentiateHelper.php | 197 +++++++++++++++--- src/Type/IntegerRangeType.php | 47 ----- src/Type/NullType.php | 7 +- tests/PHPStan/Analyser/nsrt/pow.php | 22 +- .../Analyser/nsrt/power-integer-intervals.php | 47 +++++ turbo-ext/src/IntegerRangeType.cpp | 81 ------- turbo-ext/src/IntegerType.cpp | 5 - turbo-ext/src/NullType.cpp | 19 +- turbo-ext/src/TypeTraits.h | 1 - turbo-ext/src/generated/IntegerRangeType.h | 22 +- 11 files changed, 236 insertions(+), 218 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/power-integer-intervals.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 075d5f53b9a..e207172aaad 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1315,12 +1315,6 @@ parameters: count: 3 path: src/Type/Generic/TemplateUnionType.php - - - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/IntegerRangeType.php - - rawMessage: 'Doing instanceof PHPStan\Type\IntegerType is error-prone and deprecated. Use Type::isInteger() instead.' identifier: phpstanApi.instanceofType diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 979a91fecd3..72cbc62585b 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -2,14 +2,17 @@ namespace PHPStan\Type; +use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Turbo\ReferencedByTurboExtension; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; -use function is_float; +use function count; use function is_int; use function is_numeric; use function is_string; +use function max; use function pow; +use const PHP_INT_MIN; #[ReferencedByTurboExtension(key: 'exponentiateHelper')] final class ExponentiateHelper @@ -40,26 +43,54 @@ public static function exponentiate(Type $base, Type $exponent): Type return new ErrorType(); } + foreach ($exponent->getConstantScalarValues() as $exponentValue) { + if (is_string($exponentValue) && !is_numeric($exponentValue)) { + return new ErrorType(); + } + } + if ($base instanceof ConstantScalarType) { $result = self::exponentiateConstantScalar($base, $exponent); if ($result !== null) { return $result; } + + $baseValue = $base->getValue(); + if (is_string($baseValue) && !is_numeric($baseValue)) { + return new ErrorType(); + } } - // exponentiation of a float, stays a float - $isFloatBase = $base->isFloat()->yes(); + $baseBounds = self::getIntegerBounds($base->toNumber()); + $exponentBounds = self::getIntegerBounds($exponent->toNumber()); + if ($baseBounds !== null && $exponentBounds !== null) { + $result = self::exponentiateIntegerIntervals($baseBounds[0], $baseBounds[1], $exponentBounds[0], $exponentBounds[1]); + if ($result !== null) { + return $result; + } + } - $isLooseZero = (new ConstantIntegerType(0))->isSuperTypeOf($exponent->toNumber()); + // a float operand makes the result a float + $exponentNumberType = $exponent->toNumber(); + $isFloatResult = $base->isFloat()->yes() || $exponentNumberType->isFloat()->yes(); + + $isLooseZero = (new ConstantIntegerType(0))->isSuperTypeOf($exponentNumberType); if ($isLooseZero->yes()) { - if ($isFloatBase) { - return new ConstantFloatType(1); + if ($isFloatResult) { + return new ConstantFloatType(1.0); + } + if ($base->toNumber()->isInteger()->yes()) { + return new ConstantIntegerType(1); } - return new ConstantIntegerType(1); + return new UnionType([new ConstantIntegerType(1), new ConstantFloatType(1.0)]); + } + + if ($isFloatResult) { + return new FloatType(); } - $isLooseOne = (new ConstantIntegerType(1))->isSuperTypeOf($exponent->toNumber()); + $isLooseOne = (new ConstantIntegerType(1))->isSuperTypeOf($exponentNumberType); if ($isLooseOne->yes()) { $possibleResults = new UnionType([ new FloatType(), @@ -71,10 +102,6 @@ public static function exponentiate(Type $base, Type $exponent): Type } } - if ($isFloatBase) { - return new FloatType(); - } - return new BenevolentUnionType([ new FloatType(), new IntegerType(), @@ -83,40 +110,144 @@ public static function exponentiate(Type $base, Type $exponent): Type private static function exponentiateConstantScalar(ConstantScalarType $base, Type $exponent): ?Type { - if ($exponent instanceof IntegerRangeType) { - $min = null; - $max = null; - if ($exponent->getMin() !== null) { - $min = self::pow($base->getValue(), $exponent->getMin()); - if ($min === null) { - return new ErrorType(); - } - } - if ($exponent->getMax() !== null) { - $max = self::pow($base->getValue(), $exponent->getMax()); + if (!$exponent instanceof ConstantScalarType) { + return null; + } + + $result = self::pow($base->getValue(), $exponent->getValue()); + if ($result === null) { + return new ErrorType(); + } + + if (is_int($result)) { + return new ConstantIntegerType($result); + } + return new ConstantFloatType($result); + } + + /** + * @return array{int|null, int|null}|null + */ + private static function getIntegerBounds(Type $type): ?array + { + if ($type instanceof IntegerRangeType) { + return [$type->getMin(), $type->getMax()]; + } + + $values = $type->getConstantScalarValues(); + if (count($values) === 1 && is_int($values[0])) { + return [$values[0], $values[0]]; + } + + return null; + } + + /** + * The integers $base ** $exponent gives for an integer base and a + * non-negative integer exponent from the intervals (null is unbounded), + * or null when that is not an integer interval PHP computes without + * overflowing into a float. + */ + private static function exponentiateIntegerIntervals(?int $baseMin, ?int $baseMax, ?int $exponentMin, ?int $exponentMax): ?Type + { + if ($exponentMin === null || $exponentMin < 0) { + return null; + } + + if ($exponentMax === 0) { + return new ConstantIntegerType(1); + } + + if ($baseMin !== null && $baseMin >= 0) { + // with a non-negative base and exponent the power grows with the + // base; it grows with the exponent too except for the base 0, + // which is 1 to the power of 0 and 0 to any other power + $min = $baseMin === 0 ? 0 : self::integerPower($baseMin, $exponentMin); + if ($baseMax === null || ($baseMax > 1 && $exponentMax === null)) { + $max = null; + } elseif ($baseMax === 0) { + $max = $exponentMin === 0 ? 1 : 0; + } elseif ($exponentMax === null) { + $max = 1; + } else { + $max = self::integerPower($baseMax, $exponentMax); if ($max === null) { - return new ErrorType(); + return null; } } - if (!is_float($min) && !is_float($max)) { - return IntegerRangeType::fromInterval($min, $max); + if ($min === null) { + return null; } + + return IntegerRangeType::fromInterval($min, $max); + } + + // a negative base alternates the sign with the parity of the exponent, + // so each exponent is an interval of its own + if ($exponentMax === null || $exponentMax - $exponentMin >= InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return null; } - if ($exponent instanceof ConstantScalarType) { - $result = self::pow($base->getValue(), $exponent->getValue()); + $results = []; + for ($i = 0; $i <= $exponentMax - $exponentMin; $i++) { + // $exponentMin + $i stays within $exponentMax, unlike $exponent++ + // past PHP_INT_MAX, which would turn it into a float + $result = self::exponentiateIntegerInterval($baseMin, $baseMax, $exponentMin + $i); if ($result === null) { - return new ErrorType(); + return null; } + $results[] = $result; + } + + return TypeCombinator::union(...$results); + } + + /** + * The integers $base ** $exponent gives for an integer base from the + * interval (null is unbounded) and a non-negative integer exponent, or + * null when one of them overflows into a float. + */ + private static function exponentiateIntegerInterval(?int $baseMin, ?int $baseMax, int $exponent): ?Type + { + if ($exponent === 0) { + return new ConstantIntegerType(1); + } - if (is_int($result)) { - return new ConstantIntegerType($result); + if ($exponent % 2 === 1) { + // an odd power keeps the order of the bases + [$minBase, $maxBase] = [$baseMin, $baseMax]; + } elseif ($baseMax !== null && $baseMax <= 0) { + // an even power of non-positive bases reverses their order + [$minBase, $maxBase] = [$baseMax, $baseMin]; + } elseif ($baseMin !== null && $baseMin >= 0) { + [$minBase, $maxBase] = [$baseMin, $baseMax]; + } else { + // an even power of bases around 0 is the largest at the end + // further from 0 + if ($baseMin === PHP_INT_MIN) { + // -PHP_INT_MIN overflows into a float + return null; } - return new ConstantFloatType($result); + $minBase = 0; + $maxBase = $baseMin === null || $baseMax === null ? null : max(-$baseMin, $baseMax); } - return null; + $min = $minBase === null ? null : self::integerPower($minBase, $exponent); + $max = $maxBase === null ? null : self::integerPower($maxBase, $exponent); + if (($minBase !== null && $min === null) || ($maxBase !== null && $max === null)) { + return null; + } + + return IntegerRangeType::fromInterval($min, $max); + } + + /** $base ** $exponent, or null when it overflows into a float. */ + private static function integerPower(int $base, int $exponent): ?int + { + $result = $base ** $exponent; + + return is_int($result) ? $result : null; } private static function pow(mixed $base, mixed $exp): float|int|null diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index fc3106380e4..49b426d91e7 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -23,7 +23,6 @@ use function count; use function floor; use function get_class; -use function is_float; use function is_int; use function max; use function min; @@ -676,52 +675,6 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type - { - if ($exponent instanceof UnionType) { - $results = []; - foreach ($exponent->getTypes() as $unionType) { - $results[] = $this->arithmeticPower($unionType, $phpVersion); - } - return TypeCombinator::union(...$results); - } - - if ($exponent instanceof IntegerRangeType) { - $min = null; - $max = null; - if ($this->getMin() !== null && $exponent->getMin() !== null) { - $min = $this->getMin() ** $exponent->getMin(); - } - if ($this->getMax() !== null && $exponent->getMax() !== null) { - $max = $this->getMax() ** $exponent->getMax(); - } - - if (($min !== null || $max !== null) && !is_float($min) && !is_float($max)) { - return self::fromInterval($min, $max); - } - } - - if ($exponent instanceof ConstantScalarType) { - $exponentValue = $exponent->getValue(); - if (is_int($exponentValue)) { - $min = null; - $max = null; - if ($this->getMin() !== null) { - $min = $this->getMin() ** $exponentValue; - } - if ($this->getMax() !== null) { - $max = $this->getMax() ** $exponentValue; - } - - if (!is_float($min) && !is_float($max)) { - return self::fromInterval($min, $max); - } - } - } - - return parent::arithmeticPower($exponent, $phpVersion); - } - /** * @return list */ diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 7e3331691fe..6c88b5c3b01 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -420,12 +420,7 @@ public function getFiniteTypes(): array public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return new UnionType( - [ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], - ); + return ExponentiateHelper::exponentiate($this, $exponent); } public function toPhpDocNode(): TypeNode diff --git a/tests/PHPStan/Analyser/nsrt/pow.php b/tests/PHPStan/Analyser/nsrt/pow.php index 69a7a3c2224..e0a0f85b027 100644 --- a/tests/PHPStan/Analyser/nsrt/pow.php +++ b/tests/PHPStan/Analyser/nsrt/pow.php @@ -70,8 +70,8 @@ function foo($positiveInt, $range2, $unionRange1, $unionRange2): void { assertType('int<2, max>', pow($range, $positiveInt)); assertType('int<2, max>', $range ** $positiveInt); - assertType('int', pow($range, $range2)); - assertType('int', $range ** $range2); + assertType('(float|int)', pow($range, $range2)); + assertType('(float|int)', $range ** $range2); assertType('(float|int)', pow($range, PHP_INT_MAX)); assertType('(float|int)', $range ** PHP_INT_MAX); @@ -82,8 +82,8 @@ function foo($positiveInt, $range2, $unionRange1, $unionRange2): void { assertType('(float|int)', pow($positiveInt, $range2)); assertType('(float|int)', $positiveInt ** $range2); - assertType('int<-6, 16>|int<1296, 4096>', pow($unionRange1, $unionRange2)); - assertType('int<-6, 16>|int<1296, 4096>', $unionRange1 ** $unionRange2); + assertType('int<-7776, -1024>|int<-32, -1>|int<1, 64>|int<256, 1296>|int<4096, 46656>', pow($unionRange1, $unionRange2)); + assertType('int<-7776, -1024>|int<-32, -1>|int<1, 64>|int<256, 1296>|int<4096, 46656>', $unionRange1 ** $unionRange2); assertType('int<2, 4>|int<16, 64>', pow(2, $unionRange2)); assertType('int<2, 4>|int<16, 64>', 2 ** $unionRange2); @@ -94,8 +94,8 @@ function foo($positiveInt, $range2, $unionRange1, $unionRange2): void { assertType('1', pow(true, $unionRange2)); assertType('1', true ** $unionRange2); - assertType('0|1', pow(null, $unionRange2)); - assertType('0|1', null ** $unionRange2); + assertType('0', pow(null, $unionRange2)); + assertType('0', null ** $unionRange2); } /** @@ -124,8 +124,8 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('*ERROR*', pow($bool, $arr)); assertType('*ERROR*', pow($bool, [])); - assertType('0|1', pow(null, "123")); - assertType('0|1', pow(null, $intA)); + assertType('0', pow(null, "123")); + assertType('(float|int)', pow(null, $intA)); assertType('1', "123" ** null); assertType('1', $intA ** null); assertType('1.0', $float ** null); @@ -162,9 +162,9 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('NAN', pow(-1,5.5)); - assertType('1', pow($s, 0)); - assertType('1', $s ** '0'); - assertType('1', $s ** false); + assertType('1|1.0', pow($s, 0)); + assertType('1|1.0', $s ** '0'); + assertType('1|1.0', $s ** false); assertType('(float|int)', pow($s, 1)); assertType('(float|int)', $s ** '1'); assertType('*ERROR*', $s ** $arr); diff --git a/tests/PHPStan/Analyser/nsrt/power-integer-intervals.php b/tests/PHPStan/Analyser/nsrt/power-integer-intervals.php new file mode 100644 index 00000000000..1d61266e0f5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/power-integer-intervals.php @@ -0,0 +1,47 @@ + $zeroToTen + * @param int<0, 3> $zeroToThree + * @param int<-5, 5> $minusFiveToFive + * @param int<-3, -2> $minusThreeToMinusTwo + * @param int $negative + * @param int<-9223372036854775808, 5> $fromIntMin + * @param numeric-string $numericString + */ +function doFoo(int $int, float $float, $zeroToTen, $zeroToThree, $minusFiveToFive, $minusThreeToMinusTwo, $negative, $fromIntMin, string $numericString): void +{ + // null and false are 0 as a base: 0 ** 0 is 1, 0 ** 1 is 0 + assertType('int<0, 1>', null ** $zeroToTen); + assertType('int<0, 1>', false ** $zeroToTen); + assertType('int<0, 1>', 0 ** $zeroToTen); + assertType('int<0, 10000000000>', $zeroToTen ** $zeroToTen); + assertType('int<1, 1024>', 2 ** $zeroToTen); + + // a negative base alternates its sign + assertType('-1|1', (-1) ** $zeroToTen); + assertType('int<0, 25>', $minusFiveToFive ** 2); + assertType('int<-125, 125>', $minusFiveToFive ** 3); + assertType('int<4, 9>', $minusThreeToMinusTwo ** 2); + assertType('int<-27, -8>', $minusThreeToMinusTwo ** 3); + assertType('1|int<-27, -8>|int<-3, -2>|int<4, 9>', $minusThreeToMinusTwo ** $zeroToThree); + assertType('int', $negative ** 9223372036854775807); + assertType('(float|int)', $fromIntMin ** 2); + + // a float operand makes the result a float + assertType('float', null ** $float); + assertType('float', $int ** $float); + + // anything to the power of 0 is 1, or 1.0 for a float base + assertType('1', $int ** 0); + assertType('1.0', $float ** 0); + assertType('(1|1.0)', $numericString ** 0); + + // a non-numeric string operand is a TypeError + assertType('*ERROR*', '' ** $int); + assertType('*ERROR*', $int ** 'abc'); +} diff --git a/turbo-ext/src/IntegerRangeType.cpp b/turbo-ext/src/IntegerRangeType.cpp index 1d41c34f696..1f1b2a7372f 100644 --- a/turbo-ext/src/IntegerRangeType.cpp +++ b/turbo-ext/src/IntegerRangeType.cpp @@ -838,86 +838,6 @@ class IntegerRangeType return zv::Val::null(); } - /* the union over a UnionType exponent's members; the bounds raised to - * an IntegerRangeType exponent's bounds or a constant int exponent - * when every power stays an int; ExponentiateHelper otherwise; UNDEF = - * pending exception */ - zv::Val arithmeticPower(zval *exponent, zval *phpVersion) const - { - bool isUnion; - if (UNEXPECTED(!pt_union_type_instanceof(exponent, isUnion))) return zv::Val(); - if (isUnion) { - zv::Val types = pt_type_call(Z_OBJ_P(exponent), PT_LC("gettypes"), 0, NULL); - if (UNEXPECTED(types.isUndef())) return zv::Val(); - if (UNEXPECTED(!zv::Ref(types.raw()).isArray())) { - zend_type_error("phpstan_turbo: UnionType::getTypes() must return array"); - return zv::Val(); - } - zv::Arr results = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(types.raw()))); - for (zv::ArrayEntry entry : zv::ArrRef(types.raw())) { - zval *unionType = entry.value().deref().raw(); - if (UNEXPECTED(Z_TYPE_P(unionType) != IS_OBJECT)) { - zend_type_error("phpstan_turbo: UnionType::getTypes() must return %s instances", ZSTR_VAL(pt_ce_integer_type->name)); - return zv::Val(); - } - /* $this->arithmeticPower($unionType, $phpVersion) */ - zv::Args args{unionType, phpVersion}; - zv::Val result = isExact() ? arithmeticPower(unionType, phpVersion) : pt_type_call(self, PT_LC("arithmeticpower"), 2, args); - if (UNEXPECTED(result.isUndef())) return zv::Val(); - results.push(std::move(result)); - } - return pt_type_combinator_call_spread(PT_LC("union"), results.table()); - } - - /* $this->getMin() / $this->getMax() — through the object's class */ - NullableLong min, max; - if (UNEXPECTED(!pt_integer_range_bounds(self, min, max))) return zv::Val(); - - if (instanceof_function(Z_OBJCE_P(exponent), pt_ce_integer_range_type)) { - NullableLong exponentMin, exponentMax; - if (UNEXPECTED(!pt_integer_range_bounds(Z_OBJ_P(exponent), exponentMin, exponentMax))) return zv::Val(); - zval poweredMin, poweredMax; - ZVAL_NULL(&poweredMin); - ZVAL_NULL(&poweredMax); - if (!min.isNull && !exponentMin.isNull) { - if (UNEXPECTED(!phpPow(min.value, exponentMin.value, &poweredMin))) return zv::Val(); - } - if (!max.isNull && !exponentMax.isNull) { - if (UNEXPECTED(!phpPow(max.value, exponentMax.value, &poweredMax))) return zv::Val(); - } - - if ((Z_TYPE(poweredMin) != IS_NULL || Z_TYPE(poweredMax) != IS_NULL) && Z_TYPE(poweredMin) != IS_DOUBLE && Z_TYPE(poweredMax) != IS_DOUBLE) { - return fromInterval(NullableLong::from(&poweredMin), NullableLong::from(&poweredMax), 0); - } - } - - bool isConstantScalar; - if (UNEXPECTED(!pt_type_instanceof(exponent, PT_CLASS_CONSTANT_SCALAR_TYPE, isConstantScalar))) return zv::Val(); - if (isConstantScalar) { - zv::Val exponentValue = pt_type_call(Z_OBJ_P(exponent), PT_LC("getvalue"), 0, NULL); - if (UNEXPECTED(exponentValue.isUndef())) return zv::Val(); - if (zv::Ref(exponentValue.raw()).isLong()) { - zend_long e = zv::Ref(exponentValue.raw()).asLong(); - zval poweredMin, poweredMax; - ZVAL_NULL(&poweredMin); - ZVAL_NULL(&poweredMax); - if (!min.isNull) { - if (UNEXPECTED(!phpPow(min.value, e, &poweredMin))) return zv::Val(); - } - if (!max.isNull) { - if (UNEXPECTED(!phpPow(max.value, e, &poweredMax))) return zv::Val(); - } - - if (Z_TYPE(poweredMin) != IS_DOUBLE && Z_TYPE(poweredMax) != IS_DOUBLE) { - return fromInterval(NullableLong::from(&poweredMin), NullableLong::from(&poweredMax), 0); - } - } - } - - /* parent::arithmeticPower($exponent, $phpVersion) */ - return pt_integer_type_arithmetic_power(self, exponent, phpVersion); - } - /* every integer of a bounded range as a ConstantIntegerType, unless * there are more than InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT * of them (or the range is unbounded): then []; UNDEF = pending @@ -1497,7 +1417,6 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) pt_irt_try(INTERNAL_FUNCTION_PARAM_PASSTHRU, &IntegerRangeType::tryRemove); }); - cls.method<&IntegerRangeType::arithmeticPower, zp::Obj, zp::Obj>(sigs::arithmeticPower); cls.method<&IntegerRangeType::getFiniteTypes>(sigs::getFiniteTypes); diff --git a/turbo-ext/src/IntegerType.cpp b/turbo-ext/src/IntegerType.cpp index 3114077faa5..e031c105de4 100644 --- a/turbo-ext/src/IntegerType.cpp +++ b/turbo-ext/src/IntegerType.cpp @@ -233,11 +233,6 @@ zv::Val pt_integer_type_loose_compare(zend_object *self, zval *type, zval *phpVe return IntegerType::looseCompare(type, phpVersion); } -zv::Val pt_integer_type_arithmetic_power(zend_object *self, zval *exponent, zval *phpVersion) -{ - return IntegerType(self).arithmeticPower(exponent, phpVersion); -} - /* {{{ engine ABI glue: parameter parsing + registration */ #define PT_THIS IntegerType(Z_OBJ_P(ZEND_THIS)) diff --git a/turbo-ext/src/NullType.cpp b/turbo-ext/src/NullType.cpp index 5dd13944056..d58ecaf4893 100644 --- a/turbo-ext/src/NullType.cpp +++ b/turbo-ext/src/NullType.cpp @@ -304,19 +304,6 @@ class NullType /* [$this] */ zv::Val getFiniteTypes() const { return getConstantScalarTypes(); } - /* new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(1)]) */ - static zv::Val arithmeticPower() - { - zv::Val zero = pt_type_new_constant_integer(0); - if (UNEXPECTED(zero.isUndef())) return zv::Val(); - zv::Val one = pt_type_new_constant_integer(1); - if (UNEXPECTED(one.isUndef())) return zv::Val(); - zv::Arr types = zv::Arr::create(2); - types.push(std::move(zero)); - types.push(std::move(one)); - return pt_type_new_union(std::move(types)); - } - /* new IdentifierTypeNode('null') */ static zv::Val toPhpDocNode() { @@ -703,10 +690,14 @@ PT_MINIT_REGISTRATION(pt_register_null_type) cls.method<&NullType::getFiniteTypes>(sigs::getFiniteTypes); + /* ExponentiateHelper::exponentiate($this, $exponent) */ cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { zval *exponent, *phpVersion; if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); - PT_RETURN_VAL(NullType::arithmeticPower()); + zval self; + ZVAL_OBJ(&self, Z_OBJ_P(ZEND_THIS)); + zv::Args args{&self, exponent}; + PT_RETURN_VAL(pt_type_call_static(PT_CLASS_EXPONENTIATE_HELPER, PT_LC("exponentiate"), 2, args)); }); cls.method<&NullType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 6dc28d7c8da..183c84abc1e 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -272,7 +272,6 @@ zv::Val pt_integer_range_create_all_greater_than_or_equal_to(zval *value); * (IntegerType.cpp), for the children's parent:: calls; UNDEF = pending * exception */ zv::Val pt_integer_type_loose_compare(zend_object *self, zval *type, zval *phpVersion); -zv::Val pt_integer_type_arithmetic_power(zend_object *self, zval *exponent, zval *phpVersion); /* the bodies of StringType's offset methods and tryRemove() (StringType.cpp), * for the children's parent:: calls; the setOffsetValueType() body takes diff --git a/turbo-ext/src/generated/IntegerRangeType.h b/turbo-ext/src/generated/IntegerRangeType.h index a439446e031..cee0ed75748 100644 --- a/turbo-ext/src/generated/IntegerRangeType.h +++ b/turbo-ext/src/generated/IntegerRangeType.h @@ -98,12 +98,10 @@ inline constexpr char strings[] = "tryIntersect\0" /* 832 */ "typeToRemove\0" /* 845 */ "tryRemove\0" /* 858 */ - "exponent\0" /* 868 */ - "arithmeticPower\0" /* 877 */ - "getFiniteTypes\0" /* 893 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 908 */ - "toPhpDocNode\0" /* 947 */ - "looseCompare"; /* 960 */ + "getFiniteTypes\0" /* 868 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 883 */ + "toPhpDocNode\0" /* 922 */ + "looseCompare"; /* 935 */ 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 */ @@ -177,11 +175,8 @@ inline constexpr reg::PackedArg args[] = { reg::packed(28, MAY_BE_NULL, 29), /* tryIntersect return */ reg::packed(845, 0, 29), /* tryRemove $typeToRemove */ reg::packed(28, MAY_BE_NULL, 29), /* tryRemove return */ - reg::packed(868, 0, 29), /* arithmeticPower $exponent */ - reg::packed(546, 0, 557), /* arithmeticPower $phpVersion */ - reg::packed(28, 0, 29), /* arithmeticPower return */ reg::packed(28, MAY_BE_ARRAY), /* getFiniteTypes return */ - reg::packed(28, 0, 908), /* toPhpDocNode return */ + reg::packed(28, 0, 883), /* toPhpDocNode return */ reg::packed(287, 0, 29), /* looseCompare $type */ reg::packed(546, 0, 557), /* looseCompare $phpVersion */ reg::packed(28, 0, 745), /* looseCompare return */ @@ -224,10 +219,9 @@ inline constexpr sigtab::Sig toString = { { 814 /* toString */, 0, 65, 0, 65, ZE inline constexpr sigtab::Sig tryUnion = { { 823 /* tryUnion */, 1, 66, 1, 67, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryIntersect = { { 832 /* tryIntersect */, 1, 68, 1, 69, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig tryRemove = { { 858 /* tryRemove */, 1, 70, 1, 71, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticPower = { { 877 /* arithmeticPower */, 2, 72, 2, 74, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 893 /* getFiniteTypes */, 0, 75, 0, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 947 /* toPhpDocNode */, 0, 76, 0, 76, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 960 /* looseCompare */, 2, 77, 2, 79, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 868 /* getFiniteTypes */, 0, 72, 0, 72, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 922 /* toPhpDocNode */, 0, 73, 0, 73, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 935 /* looseCompare */, 2, 74, 2, 76, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntegerRangeType From 52c7c5101c5b4b458bfdcabef36459d3a9d589e9 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 18:48:56 +0000 Subject: [PATCH 06/14] Keep an integer concatenated with a string numeric only for digits An integer followed by ' 1' or '+5' is not numeric although those strings are. The appended string now has to be digits with an optional fraction and exponent, and neither check accepts a trailing newline, which PHP 7 does not allow in numeric strings. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 7 +++-- .../Analyser/nsrt/concat-numeric-string.php | 24 ++++++++++++++++ turbo-ext/src/IetrSupport.h | 28 +++++++++++++++++-- turbo-ext/src/OperatorTypeHelper.cpp | 2 +- turbo-ext/tests/type-family.php | 8 +++--- 5 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/concat-numeric-string.php diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 0b5def84fc8..44237bf8273 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -36,7 +36,6 @@ use function is_numeric; use function max; use function min; -use function str_starts_with; use const INF; use const PHP_INT_MAX; use const PHP_INT_MIN; @@ -129,9 +128,11 @@ public static function concat(Type $left, Type $right, PhpVersion $phpVersion): $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); if ($leftNumericStringNonEmpty->isNumericString()->yes()) { + // after the digits of an integer, a sign or a whitespace is not numeric, + // and PHP 7 does not allow a trailing newline $validationCallback = $left->isInteger()->yes() - ? static fn (string $value): bool => !str_starts_with($value, '-') - : static fn (string $value): bool => Strings::match($value, '#^\d+$#') !== null; + ? static fn (string $value): bool => Strings::match($value, '#^\d+(\.\d*)?([eE][+-]?\d+)?\z#') !== null + : static fn (string $value): bool => Strings::match($value, '#^\d+\z#') !== null; $allRightConstantsZeroOrMore = false; foreach ($rightConstantStrings as $rightConstantString) { diff --git a/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php b/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php new file mode 100644 index 00000000000..bf292f00e4d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php @@ -0,0 +1,24 @@ + $nonNegative + * @param numeric-string $numericString + */ +function doFoo(int $int, $nonNegative, string $numericString): void +{ + // an integer followed by digits stays numeric + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $int . '5'); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $int . $nonNegative); + + // '1 1' and '1+5' are not numeric, although ' 1' and '+5' are + assertType('lowercase-string&non-falsy-string&uppercase-string', $int . ' 1'); + assertType('lowercase-string&non-falsy-string&uppercase-string', $int . '+5'); + + // PHP 7 does not allow trailing whitespace in numeric strings + assertType('lowercase-string&non-falsy-string&uppercase-string', $int . "5\n"); + assertType('non-falsy-string', $numericString . "5\n"); +} diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h index e964097fa0e..09020c899ad 100644 --- a/turbo-ext/src/IetrSupport.h +++ b/turbo-ext/src/IetrSupport.h @@ -583,12 +583,10 @@ inline int isEmptyConstantString(zval *type) return constantStringValueIsEmpty(type); } -/* Strings::match($value, '#^\d+$#') !== null: ASCII digits, a final - * newline allowed before the end ($ without D) */ +/* Strings::match($value, '#^\d+\z#') !== null: ASCII digits only */ inline bool isDecimalDigits(zend_string *value) { size_t length = ZSTR_LEN(value); - if (length > 0 && ZSTR_VAL(value)[length - 1] == '\n') length--; if (length == 0) return false; for (size_t i = 0; i < length; i++) { char c = ZSTR_VAL(value)[i]; @@ -597,6 +595,30 @@ inline bool isDecimalDigits(zend_string *value) return true; } +/* Strings::match($value, '#^\d+(\.\d*)?([eE][+-]?\d+)?\z#') !== null: + * digits, an optional fraction and an optional exponent, nothing else */ +inline bool isNumberAfterInteger(zend_string *value) +{ + const char *c = ZSTR_VAL(value); + const char *end = c + ZSTR_LEN(value); + auto digits = [&c, end]() -> size_t { + const char *start = c; + while (c < end && *c >= '0' && *c <= '9') c++; + return (size_t) (c - start); + }; + if (digits() == 0) return false; + if (c < end && *c == '.') { + c++; + digits(); + } + if (c < end && (*c == 'e' || *c == 'E')) { + c++; + if (c < end && (*c == '+' || *c == '-')) c++; + if (digits() == 0) return false; + } + return c == end; +} + /* $type instanceof ConstantIntegerType && $type->getValue() === 0; -1 = * pending exception */ inline int isConstantIntegerZero(zval *type) diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index ec4c10e3125..24d397ed6a2 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -190,7 +190,7 @@ class OperatorTypeHelper if ( is_numeric_string(ZSTR_VAL(string), ZSTR_LEN(string), NULL, NULL, false) == 0 - || !(integerValidation ? ZSTR_VAL(string)[0] != '-' : isDecimalDigits(string)) + || !(integerValidation ? isNumberAfterInteger(string) : isDecimalDigits(string)) ) { allRightConstantsZeroOrMore = false; break; diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index 8fff803e6dd..9b20b44cc7a 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7797,8 +7797,8 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu 'mod' => '%', ]; $integerOperands = ['int0' => $int(0), 'int7' => $int(7), 'int-3' => $int(-3), 'intMax' => $int(PHP_INT_MAX), 'intMin' => $int(PHP_INT_MIN), 'range0-10' => $range(0, 10), 'rangeMin--1' => $range(null, -1), 'range5-max' => $range(5, null), 'range-4-6' => $range(-4, 6), 'rangeMin-max' => $range(-1000, null), 'unionRangeConst' => $operands['unionRangeConst'], 'benevolentRanges' => new \PHPStan\Type\BenevolentUnionType([$range(0, 3), $int(-8)])]; - $overflowingOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809')]; - foreach ($integerOperands + $overflowingOperands as $name => $operand) { + $numericStringOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809'), 'leadingSpace' => new \PHPStan\Type\Constant\ConstantStringType(' 1'), 'plusSign' => new \PHPStan\Type\Constant\ConstantStringType('+5'), 'trailingNewline' => new \PHPStan\Type\Constant\ConstantStringType("5\n"), 'exponent' => new \PHPStan\Type\Constant\ConstantStringType('1.5e3')]; + foreach ($integerOperands + $numericStringOperands as $name => $operand) { foreach ($operand->getConstantScalarTypes() as $i => $constant) { $r["toIntegerOperand $name $i"] = $catching(static fn () => $staticPrivate('toIntegerOperand', $constant)); } @@ -7845,9 +7845,9 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu $operatorMethods = ['arithmeticAdd', 'arithmeticSubtract', 'arithmeticMultiply', 'arithmeticDivide', 'arithmeticIntDivide', 'arithmeticModulo', 'arithmeticPower', 'bitwiseAnd', 'bitwiseOr', 'bitwiseXor', 'bitwiseShiftLeft', 'bitwiseShiftRight', 'stringConcatenate', 'arrayUnion']; foreach ([70400, 80500] as $operatorVersionId) { $operatorVersion = new \PHPStan\Php\PhpVersion($operatorVersionId); - foreach ($operands + $integerOperands + $overflowingOperands as $leftName => $left) { + foreach ($operands + $integerOperands + $numericStringOperands as $leftName => $left) { $r["arithmeticNegate $operatorVersionId $leftName"] = $catching(static fn () => $left->arithmeticNegate($operatorVersion)); - foreach ($operands + $integerOperands + $overflowingOperands as $rightName => $right) { + foreach ($operands + $integerOperands + $numericStringOperands as $rightName => $right) { foreach ($operatorMethods as $operatorMethod) { $r["$operatorMethod $operatorVersionId $leftName $rightName"] = $catching(static fn () => $left->$operatorMethod($right, $operatorVersion)); } From bd2e9da549e907e8e9cf3ee86b8d159751b4133d Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 18:59:14 +0000 Subject: [PATCH 07/14] Subtract integer ranges as intervals A constant minus a range unbounded on either side was int, which is wrong for a negative operand (0 - int is int<1, max>, not int), and the other cases took the bounds from mismatched ends before swapping them. [a, b] - [c, d] is [a - d, b - c]. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 6 --- src/Type/OperatorTypeHelper.php | 53 ++----------------- .../Analyser/nsrt/integer-range-types.php | 4 +- tests/PHPStan/Analyser/nsrt/math.php | 6 +-- .../Analyser/nsrt/subtract-integer-range.php | 23 ++++++++ turbo-ext/src/OperatorTypeHelper.cpp | 50 +++-------------- 6 files changed, 39 insertions(+), 103 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/subtract-integer-range.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e207172aaad..52a4af6c39d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1501,12 +1501,6 @@ parameters: count: 1 path: src/Type/OperatorTypeHelper.php - - - rawMessage: PHPDoc tag @var with type float|int is not subtype of type int. - identifier: varTag.type - count: 4 - path: src/Type/OperatorTypeHelper.php - - rawMessage: PHPDoc tag @var with type float|int|null is not subtype of type int|null. identifier: varTag.type diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 44237bf8273..c80845b3654 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -1300,55 +1300,12 @@ private static function integerRangeMath(Type $range, string $operator, Type $op $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null; } } elseif ($operator === '-') { - if ($operand instanceof ConstantIntegerType) { - /** @var int|float|null $min */ - $min = $rangeMin !== null ? $rangeMin - $operand->getValue() : null; + // [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 ? $rangeMax - $operand->getValue() : null; - } else { - if ($rangeMin === $rangeMax && $rangeMin !== null - && ($operand->getMin() === null || $operand->getMax() === null)) { - $min = null; - $max = $rangeMin; - } else { - if ($operand->getMin() === null) { - $min = null; - } elseif ($rangeMin !== null) { - if ($operand->getMax() !== null) { - /** @var int|float $min */ - $min = $rangeMin - $operand->getMax(); - } else { - /** @var int|float $min */ - $min = $rangeMin - $operand->getMin(); - } - } else { - $min = null; - } - - if ($operand->getMax() === null) { - $min = null; - $max = null; - } elseif ($rangeMax !== null) { - if ($rangeMin !== null && $operand->getMin() === null) { - /** @var int|float $min */ - $min = $rangeMin - $operand->getMax(); - $max = null; - } elseif ($operand->getMin() !== null) { - /** @var int|float $max */ - $max = $rangeMax - $operand->getMin(); - } else { - $max = null; - } - } else { - $max = null; - } - - if ($min !== null && $max !== null && $min > $max) { - [$min, $max] = [$max, $min]; - } - } - } + /** @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); diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index d59e7d7cce3..996a001cb81 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -241,7 +241,7 @@ public function math($i, $j, $z, $pi, $r1, $r2, $r3, $rMin, $rMax, $x, $y) { assertType('int<2, max>', $pi * 2); assertType('float|int<1, max>', $pi / 2); assertType('int<2, max>', 1 + $pi); - assertType('int', 2 - $pi); + assertType('int', 2 - $pi); assertType('int<2, max>', 2 * $pi); assertType('float|int<1, 2>', 2 / $pi); @@ -271,7 +271,7 @@ public function math($i, $j, $z, $pi, $r1, $r2, $r3, $rMin, $rMax, $x, $y) { assertType('float|int', $rMin / $r1); assertType('int<6, max>', $r1 + $rMax); - assertType('int', $r1 - $rMax); + assertType('int', $r1 - $rMax); assertType('int<5, max>', $r1 * $rMax); assertType('float|int<1, 2>', $r1 / $rMax); assertType('int<6, max>', $rMax + $r1); diff --git a/tests/PHPStan/Analyser/nsrt/math.php b/tests/PHPStan/Analyser/nsrt/math.php index 99ec5fc5082..59da7168411 100644 --- a/tests/PHPStan/Analyser/nsrt/math.php +++ b/tests/PHPStan/Analyser/nsrt/math.php @@ -46,16 +46,16 @@ public function doBaz(int $rangeFiveBoth, int $rangeFiveLeft, int $rangeFiveRigh assertType('int', $rangeFiveRight - 1); assertType('int', $rangeFiveLeft + $rangeFiveRight); - assertType('int', $rangeFiveLeft - $rangeFiveRight); + assertType('int<-10, max>', $rangeFiveLeft - $rangeFiveRight); assertType('int', $rangeFiveRight + $rangeFiveLeft); - assertType('int', $rangeFiveRight - $rangeFiveLeft); + assertType('int', $rangeFiveRight - $rangeFiveLeft); assertType('int<-10, 10>', $rangeFiveBoth + $rangeFiveBoth); assertType('int<-10, 10>', $rangeFiveBoth - $rangeFiveBoth); assertType('int<-10, max>', $rangeFiveBoth + $rangeFiveLeft); - assertType('int', $rangeFiveBoth - $rangeFiveLeft); + assertType('int', $rangeFiveBoth - $rangeFiveLeft); assertType('int', $rangeFiveBoth + $rangeFiveRight); assertType('int<-10, max>', $rangeFiveBoth - $rangeFiveRight); diff --git a/tests/PHPStan/Analyser/nsrt/subtract-integer-range.php b/tests/PHPStan/Analyser/nsrt/subtract-integer-range.php new file mode 100644 index 00000000000..b551ca9c354 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/subtract-integer-range.php @@ -0,0 +1,23 @@ + $negative + * @param int $upToThree + * @param int<1, max> $positive + * @param int<-5, 5> $range + */ +function doFoo($negative, $upToThree, $positive, $range): void +{ + // [a, b] - [c, d] is [a - d, b - c] + assertType('int<1, max>', 0 - $negative); + assertType('int<1, max>', null - $negative); + assertType('int<2, max>', 5 - $upToThree); + assertType('int', 5 - $positive); + assertType('int<-8, max>', $range - $upToThree); + assertType('int', $range - $positive); + assertType('int<-10, 10>', $range - $range); +} diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 24d397ed6a2..df8efa991da 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -1528,50 +1528,12 @@ class OperatorTypeHelper } break; case IETR_MINUS: - if (operandIsConstant) { - if (Z_TYPE(rangeMin) != IS_NULL && UNEXPECTED(!scalarOp(sub_function, &rangeMin, operandValue, &min))) return zv::Val(); - if (Z_TYPE(rangeMax) != IS_NULL && UNEXPECTED(!scalarOp(sub_function, &rangeMax, operandValue, &max))) return zv::Val(); - } else { - if (identical(&rangeMin, &rangeMax) && Z_TYPE(rangeMin) != IS_NULL - && (Z_TYPE(operandMin) == IS_NULL || Z_TYPE(operandMax) == IS_NULL)) { - ZVAL_NULL(&min); - ZVAL_COPY_VALUE(&max, &rangeMin); - } else { - if (Z_TYPE(operandMin) == IS_NULL) { - ZVAL_NULL(&min); - } else if (Z_TYPE(rangeMin) != IS_NULL) { - if (Z_TYPE(operandMax) != IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); - } else { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMin, &min))) return zv::Val(); - } - } else { - ZVAL_NULL(&min); - } - - if (Z_TYPE(operandMax) == IS_NULL) { - ZVAL_NULL(&min); - ZVAL_NULL(&max); - } else if (Z_TYPE(rangeMax) != IS_NULL) { - if (Z_TYPE(rangeMin) != IS_NULL && Z_TYPE(operandMin) == IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMin, &operandMax, &min))) return zv::Val(); - ZVAL_NULL(&max); - } else if (Z_TYPE(operandMin) != IS_NULL) { - if (UNEXPECTED(!scalarOp(sub_function, &rangeMax, &operandMin, &max))) return zv::Val(); - } else { - ZVAL_NULL(&max); - } - } else { - 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); - } - } + /* [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: { From 673a58d9bfae96983f46bf0d8afec868f533e920 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 19:09:33 +0000 Subject: [PATCH 08/14] Infer a bitwise operation of a string and mixed as int|string Only two strings make a string; with an int, a float, a bool or null on the other side the string is a number, so '1' & $mixed is 1 for $mixed = 1. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 21 ++++++++++++++++--- tests/PHPStan/Analyser/nsrt/binary.php | 11 ++++++---- tests/PHPStan/Analyser/nsrt/bitwise-mixed.php | 18 ++++++++++++++++ tests/PHPStan/Analyser/nsrt/bitwise.php | 12 +++++------ turbo-ext/src/OperatorTypeHelper.cpp | 12 ++++++++++- 5 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bitwise-mixed.php diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index c80845b3654..6209328e766 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -196,7 +196,12 @@ public static function bitwiseAnd(Type $leftType, Type $rightType, PhpVersion $p ($leftIsString->yes() || $leftType instanceof MixedType) && ($rightIsString->yes() || $rightType instanceof MixedType) ) { - return new StringType(); + // with a mixed operand that is not a string, the string is a number + if ($leftIsString->yes() && $rightIsString->yes()) { + return new StringType(); + } + + return new BenevolentUnionType([new IntegerType(), new StringType()]); } if ($leftIsString->maybe() && $rightIsString->maybe()) { return new ErrorType(); @@ -244,7 +249,12 @@ public static function bitwiseOr(Type $leftType, Type $rightType, PhpVersion $ph ($leftIsString->yes() || $leftType instanceof MixedType) && ($rightIsString->yes() || $rightType instanceof MixedType) ) { - return new StringType(); + // with a mixed operand that is not a string, the string is a number + if ($leftIsString->yes() && $rightIsString->yes()) { + return new StringType(); + } + + return new BenevolentUnionType([new IntegerType(), new StringType()]); } if ($leftIsString->maybe() && $rightIsString->maybe()) { return new ErrorType(); @@ -292,7 +302,12 @@ public static function bitwiseXor(Type $leftType, Type $rightType, PhpVersion $p ($leftIsString->yes() || $leftType instanceof MixedType) && ($rightIsString->yes() || $rightType instanceof MixedType) ) { - return new StringType(); + // with a mixed operand that is not a string, the string is a number + if ($leftIsString->yes() && $rightIsString->yes()) { + return new StringType(); + } + + return new BenevolentUnionType([new IntegerType(), new StringType()]); } if ($leftIsString->maybe() && $rightIsString->maybe()) { return new ErrorType(); diff --git a/tests/PHPStan/Analyser/nsrt/binary.php b/tests/PHPStan/Analyser/nsrt/binary.php index de3b521a48c..5d598177e1e 100644 --- a/tests/PHPStan/Analyser/nsrt/binary.php +++ b/tests/PHPStan/Analyser/nsrt/binary.php @@ -41,6 +41,9 @@ public function doFoo(array $generalArray) $string2 = $string; $string3 = $string; $string4 = $string; + $stringAnd = $string; + $stringOr = $string; + $stringXor = $string; $fooString = 'foo'; $fooString1 = $fooString; @@ -355,7 +358,7 @@ public function doFoo(array $generalArray) assertType('string', $string ?? $integer); assertType('int|string', $stringOrNull ?? $integer); assertType('\'Foo\'', \Foo::class); - assertType('106', $line); + assertType('109', $line); assertType('literal-string&non-falsy-string', $dir); assertType('literal-string&non-falsy-string', $file); assertType('\'BinaryOperations\\\\NestedNamespace\'', $namespace); @@ -502,13 +505,13 @@ public function doFoo(array $generalArray) assertType('*ERROR*', "bla" ^ 3); assertType('6', "5" ^ 3); assertType('int<0, 3>', $integer6 &= 3); - assertType('*ERROR*', $string &= 3); + assertType('*ERROR*', $stringAnd &= 3); assertType('string', $string &= "x"); assertType('int', $integer7 |= 3); - assertType('*ERROR*', $string |= 3); + assertType('*ERROR*', $stringOr |= 3); assertType('string', $string |= "x"); assertType('int', $integer8 ^= 3); - assertType('*ERROR*', $string ^= 3); + assertType('*ERROR*', $stringXor ^= 3); assertType('string', $string ^= "x"); assertType('\'f\'', $fooString[0]); assertType('*ERROR*', $fooString[4]); diff --git a/tests/PHPStan/Analyser/nsrt/bitwise-mixed.php b/tests/PHPStan/Analyser/nsrt/bitwise-mixed.php new file mode 100644 index 00000000000..6337b794460 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bitwise-mixed.php @@ -0,0 +1,18 @@ + Date: Tue, 3 Nov 2026 19:15:40 +0000 Subject: [PATCH 09/14] Do not infer numeric-string for a concatenation of two possibly empty strings int|null . bool is '' for null . false, so it is not numeric although removing '' from the left side leaves a numeric string. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 6 +++++- tests/PHPStan/Analyser/nsrt/concat-numeric-string.php | 7 ++++++- turbo-ext/src/OperatorTypeHelper.cpp | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 6209328e766..08cfd547805 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -127,7 +127,11 @@ public static function concat(Type $left, Type $right, PhpVersion $phpVersion): } $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); - if ($leftNumericStringNonEmpty->isNumericString()->yes()) { + if ( + $leftNumericStringNonEmpty->isNumericString()->yes() + // '' . '' is not numeric + && $leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes() + ) { // after the digits of an integer, a sign or a whitespace is not numeric, // and PHP 7 does not allow a trailing newline $validationCallback = $left->isInteger()->yes() diff --git a/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php b/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php index bf292f00e4d..d3099ff35dc 100644 --- a/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php +++ b/tests/PHPStan/Analyser/nsrt/concat-numeric-string.php @@ -7,8 +7,9 @@ /** * @param int<0, max> $nonNegative * @param numeric-string $numericString + * @param int|null $intOrNull */ -function doFoo(int $int, $nonNegative, string $numericString): void +function doFoo(int $int, $nonNegative, string $numericString, $intOrNull, bool $bool): void { // an integer followed by digits stays numeric assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $int . '5'); @@ -18,6 +19,10 @@ function doFoo(int $int, $nonNegative, string $numericString): void assertType('lowercase-string&non-falsy-string&uppercase-string', $int . ' 1'); assertType('lowercase-string&non-falsy-string&uppercase-string', $int . '+5'); + // null . false is '', which is not numeric + assertType('lowercase-string&uppercase-string', $intOrNull . $bool); + assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $int . $bool); + // PHP 7 does not allow trailing whitespace in numeric strings assertType('lowercase-string&non-falsy-string&uppercase-string', $int . "5\n"); assertType('non-falsy-string', $numericString . "5\n"); diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index c25ed763ba1..3adb97006d8 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -170,7 +170,14 @@ class OperatorTypeHelper IETR_VAL(emptyStringType, newConstantStringType(Z_STR(emptyString))); IETR_VAL(leftNumericStringNonEmpty, pt_type_combinator_remove(leftStringType.raw(), emptyStringType.raw())); IETR_TRI(leftNumeric, triOn(leftNumericStringNonEmpty.raw(), mIsNumericString)); - if (leftNumeric == PT_TRI_YES) { + bool numericResult = leftNumeric == PT_TRI_YES; + if (numericResult) { + /* '' . '' is not numeric */ + IETR_TRI(leftNonEmpty, triOn(leftStringType.raw(), mIsNonEmptyString)); + IETR_TRI(rightNonEmpty, triOn(rightStringType.raw(), mIsNonEmptyString)); + numericResult = pt_trinary_or(leftNonEmpty, rightNonEmpty) == PT_TRI_YES; + } + if (numericResult) { IETR_TRI(leftIsInteger, triOn(left, mIsInteger)); bool integerValidation = leftIsInteger == PT_TRI_YES; From 94d91998f1f3b97110badf0f02b04d68b13c52b7 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 19:26:04 +0000 Subject: [PATCH 10/14] Compare integer ranges with non-numeric strings integer by integer on PHP 8 PHP 8 compares an integer with a non-numeric string as strings, which does not keep the order of the integers ('10' < '1abc' < '2'), so the bounds of a range do not decide the comparison: int<0, 10> < '1abc' was true although 2 < '1abc' is false. Each integer of a finite range decides it now, and an infinite range is maybe. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/IntegerRangeType.php | 54 +++++++++++ ...mpare-integer-range-non-numeric-string.php | 23 +++++ turbo-ext/src/IntegerRangeType.cpp | 93 +++++++++++++++++++ turbo-ext/src/PhpVersionAccess.cpp | 1 + turbo-ext/src/generated/IntegerRangeType.h | 77 ++++++++------- turbo-ext/src/support.h | 1 + turbo-ext/tests/type-family.php | 2 + 7 files changed, 216 insertions(+), 35 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/compare-integer-range-non-numeric-string.php diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 49b426d91e7..ca3566bbe55 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -24,6 +24,8 @@ use function floor; use function get_class; use function is_int; +use function is_numeric; +use function is_string; use function max; use function min; use function sprintf; @@ -323,6 +325,11 @@ public function generalize(GeneralizePrecision $precision): Type public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { + $unorderedComparison = $this->compareEachInteger($otherType, $phpVersion, static fn (ConstantIntegerType $integer): TrinaryLogic => $integer->isSmallerThan($otherType, $phpVersion)); + if ($unorderedComparison !== null) { + return $unorderedComparison; + } + if ($this->min === null) { $minIsSmaller = TrinaryLogic::createYes(); } else { @@ -350,6 +357,11 @@ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryL public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { + $unorderedComparison = $this->compareEachInteger($otherType, $phpVersion, static fn (ConstantIntegerType $integer): TrinaryLogic => $integer->isSmallerThanOrEqual($otherType, $phpVersion)); + if ($unorderedComparison !== null) { + return $unorderedComparison; + } + if ($this->min === null) { $minIsSmaller = TrinaryLogic::createYes(); } else { @@ -377,6 +389,11 @@ public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): T public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { + $unorderedComparison = $this->compareEachInteger($otherType, $phpVersion, static fn (ConstantIntegerType $integer): TrinaryLogic => $otherType->isSmallerThan($integer, $phpVersion)); + if ($unorderedComparison !== null) { + return $unorderedComparison; + } + if ($this->min === null) { $minIsSmaller = TrinaryLogic::createNo(); } else { @@ -404,6 +421,11 @@ public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryL public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { + $unorderedComparison = $this->compareEachInteger($otherType, $phpVersion, static fn (ConstantIntegerType $integer): TrinaryLogic => $otherType->isSmallerThanOrEqual($integer, $phpVersion)); + if ($unorderedComparison !== null) { + return $unorderedComparison; + } + if ($this->min === null) { $minIsSmaller = TrinaryLogic::createNo(); } else { @@ -429,6 +451,38 @@ public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): T return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } + /** + * PHP 8 compares an integer with a non-numeric string as strings, which + * does not keep the order of the integers ('10' < '1abc' < '2'), so the + * bounds do not decide the comparison then and each integer does. + * + * @param callable(ConstantIntegerType): TrinaryLogic $compare + */ + private function compareEachInteger(Type $otherType, PhpVersion $phpVersion, callable $compare): ?TrinaryLogic + { + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + return null; + } + + $hasNonNumericString = false; + foreach ($otherType->getConstantScalarValues() as $value) { + if (is_string($value) && !is_numeric($value)) { + $hasNonNumericString = true; + break; + } + } + if (!$hasNonNumericString) { + return null; + } + + $integers = $this->getFiniteTypes(); + if ($integers === []) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::extremeIdentity(...array_map($compare, $integers)); + } + public function getSmallerType(PhpVersion $phpVersion): Type { $subtractedTypes = [ diff --git a/tests/PHPStan/Analyser/nsrt/compare-integer-range-non-numeric-string.php b/tests/PHPStan/Analyser/nsrt/compare-integer-range-non-numeric-string.php new file mode 100644 index 00000000000..2769394f471 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/compare-integer-range-non-numeric-string.php @@ -0,0 +1,23 @@ + $zeroToTen + * @param int<2, 9> $twoToNine + */ +function doFoo($zeroToTen, $twoToNine): void +{ + // PHP 8 compares an integer with a non-numeric string as strings, which + // does not keep the order of the integers: '10' < '1abc' < '2' + assertType('bool', '1abc' < $zeroToTen); + assertType('bool', '1abc' <= $zeroToTen); + assertType('bool', '1abc' > $zeroToTen); + assertType('bool', '1abc' >= $zeroToTen); + assertType('bool', $zeroToTen < '1abc'); + assertType('bool', $zeroToTen >= '1abc'); + assertType('true', $twoToNine > '1abc'); + assertType('false', $twoToNine <= '1abc'); +} diff --git a/turbo-ext/src/IntegerRangeType.cpp b/turbo-ext/src/IntegerRangeType.cpp index 1f1b2a7372f..713b02fe990 100644 --- a/turbo-ext/src/IntegerRangeType.cpp +++ b/turbo-ext/src/IntegerRangeType.cpp @@ -474,6 +474,55 @@ class IntegerRangeType /* new IntegerType() */ static zv::Val generalize() { return integer(); } + /* compareEachInteger(): when PHP 8 compares the integers with a + * non-numeric constant string of $otherType as strings, which does not + * keep their order, `compare` of each integer of a finite range combined + * by extremeIdentity (maybe for an infinite range); 0 = the bounds + * decide, 1 = `out` set, -1 = pending exception */ + template + [[nodiscard]] int compareEachInteger(zval *otherType, zval *phpVersion, Compare compare, zend_long &out) const + { + bool castsNumbersToStrings; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, castsNumbersToStrings))) return -1; + if (!castsNumbersToStrings) return 0; + + zv::Val values = pt_type_call(Z_OBJ_P(otherType), PT_LC("getconstantscalarvalues"), 0, NULL); + if (UNEXPECTED(values.isUndef())) return -1; + if (UNEXPECTED(Z_TYPE_P(values.raw()) != IS_ARRAY)) { + zend_type_error("phpstan_turbo: getConstantScalarValues() must return array"); + return -1; + } + bool hasNonNumericString = false; + for (zv::ArrayEntry entry : zv::ArrRef(values.raw())) { + zval *value = entry.value().raw(); + if (Z_TYPE_P(value) == IS_STRING && is_numeric_string(Z_STRVAL_P(value), Z_STRLEN_P(value), NULL, NULL, false) == 0) { + hasNonNumericString = true; + break; + } + } + if (!hasNonNumericString) return 0; + + zv::Val integers = isExact() ? getFiniteTypes() : pt_type_call(self, PT_LC("getfinitetypes"), 0, NULL); + if (UNEXPECTED(integers.isUndef())) return -1; + if (UNEXPECTED(Z_TYPE_P(integers.raw()) != IS_ARRAY)) { + zend_type_error("phpstan_turbo: getFiniteTypes() must return array"); + return -1; + } + if (zend_hash_num_elements(Z_ARRVAL_P(integers.raw())) == 0) { + out = PT_TRI_MAYBE; + return 1; + } + + bool first = true; + for (zv::ArrayEntry entry : zv::ArrRef(integers.raw())) { + zend_long result = compare(entry.value().raw()); + if (UNEXPECTED(result < 0)) return -1; + out = first || out == result ? result : PT_TRI_MAYBE; + first = false; + } + return 1; + } + /* isSmallerThan() / isSmallerThanOrEqual(): the bounds as * ConstantIntegerTypes compared against $otherType (an unbounded min * is smaller, an unbounded max is not), and 0's own comparison when it @@ -483,6 +532,16 @@ class IntegerRangeType { const char *method = orEqual ? "issmallerthanorequal" : "issmallerthan"; size_t methodLen = orEqual ? sizeof("issmallerthanorequal") - 1 : sizeof("issmallerthan") - 1; + { + /* $integer->isSmallerThan[OrEqual]($otherType, $phpVersion) */ + zend_long unordered; + int each = compareEachInteger(otherType, phpVersion, [&](zval *integer) -> zend_long { + zv::Args args{otherType, phpVersion}; + return pt_type_call_trinary(Z_OBJ_P(integer), method, methodLen, 2, args); + }, unordered); + if (UNEXPECTED(each < 0)) return -1; + if (each > 0) return unordered; + } NullableLong min = NullableLong::null(), max = NullableLong::null(); if (UNEXPECTED(!bounds(min, max))) return -1; zv::Args args{otherType, phpVersion}; @@ -524,6 +583,16 @@ class IntegerRangeType { const char *method = orEqual ? "issmallerthanorequal" : "issmallerthan"; size_t methodLen = orEqual ? sizeof("issmallerthanorequal") - 1 : sizeof("issmallerthan") - 1; + { + /* $otherType->isSmallerThan[OrEqual]($integer, $phpVersion) */ + zend_long unordered; + int each = compareEachInteger(otherType, phpVersion, [&](zval *integer) -> zend_long { + zv::Args args{integer, phpVersion}; + return pt_type_call_trinary(Z_OBJ_P(otherType), method, methodLen, 2, args); + }, unordered); + if (UNEXPECTED(each < 0)) return -1; + if (each > 0) return unordered; + } NullableLong min = NullableLong::null(), max = NullableLong::null(); if (UNEXPECTED(!bounds(min, max))) return -1; @@ -1365,6 +1434,30 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) PT_RETURN_VAL(IntegerRangeType::generalize()); }); + /* the private compareEachInteger() with its $compare callable */ + cls.method(sigs::compareEachInteger, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *otherType, *phpVersion, *compare; + if (!zp::parse(execute_data, otherType, phpVersion, compare)) RETURN_THROWS(); + if (UNEXPECTED(!zend_is_callable(compare, 0, NULL))) { + zend_argument_type_error(3, "must be of type callable, %s given", zend_zval_value_name(compare)); + RETURN_THROWS(); + } + zend_long out = PT_TRI_MAYBE; + int each = PT_THIS.compareEachInteger(otherType, phpVersion, [compare](zval *integer) -> zend_long { + zv::Args args{integer}; + zv::Val result = pt_type_call_callable(compare, 1, args); + if (UNEXPECTED(result.isUndef())) return -1; + if (UNEXPECTED(Z_TYPE_P(result.raw()) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(result.raw()), pt_ce_trinary))) { + zend_type_error("phpstan_turbo: the comparison must return TrinaryLogic"); + return -1; + } + return pt_trinary_value(Z_OBJ_P(result.raw())); + }, out); + if (UNEXPECTED(each < 0)) RETURN_THROWS(); + if (each == 0) RETURN_NULL(); + ZVAL_COPY(return_value, pt_trinary_singleton(out)); + }); + cls.method(sigs::isSmallerThan, [](INTERNAL_FUNCTION_PARAMETERS) { pt_irt_comparison(INTERNAL_FUNCTION_PARAM_PASSTHRU, false, false); }); diff --git a/turbo-ext/src/PhpVersionAccess.cpp b/turbo-ext/src/PhpVersionAccess.cpp index af23c45a53d..809d462b80a 100644 --- a/turbo-ext/src/PhpVersionAccess.cpp +++ b/turbo-ext/src/PhpVersionAccess.cpp @@ -39,6 +39,7 @@ const VersionQuery pt_pva_queries[PT_PHP_VERSION_QUERY_COUNT] = { /* PT_PHP_VERSION_SUPPORTS_ASYMMETRIC_VISIBILITY */ {PT_LC("supportsasymmetricvisibility"), "supportsAsymmetricVisibility", 80400, false}, /* PT_PHP_VERSION_SUPPORTS_ENUMS */ {PT_LC("supportsenums"), "supportsEnums", 80100, false}, /* PT_PHP_VERSION_SUPPORTS_PROPERTY_HOOKS */ {PT_LC("supportspropertyhooks"), "supportsPropertyHooks", 80400, false}, + /* PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON */ {PT_LC("castsnumberstostringsonloosecomparison"), "castsNumbersToStringsOnLooseComparison", 80000, false}, }; pt_method_site pt_pva_query_sites[PT_PHP_VERSION_QUERY_COUNT]; diff --git a/turbo-ext/src/generated/IntegerRangeType.h b/turbo-ext/src/generated/IntegerRangeType.h index cee0ed75748..15ebc3b37f7 100644 --- a/turbo-ext/src/generated/IntegerRangeType.h +++ b/turbo-ext/src/generated/IntegerRangeType.h @@ -85,23 +85,25 @@ inline constexpr char strings[] = "isSmallerThanOrEqual\0" /* 615 */ "isGreaterThan\0" /* 636 */ "isGreaterThanOrEqual\0" /* 650 */ - "getSmallerType\0" /* 671 */ - "getSmallerOrEqualType\0" /* 686 */ - "getGreaterType\0" /* 708 */ - "getGreaterOrEqualType\0" /* 723 */ - "PHPStan\\Type\\BooleanType\0" /* 745 */ - "toBoolean\0" /* 770 */ - "toAbsoluteNumber\0" /* 780 */ - "toBitwiseNotType\0" /* 797 */ - "toString\0" /* 814 */ - "tryUnion\0" /* 823 */ - "tryIntersect\0" /* 832 */ - "typeToRemove\0" /* 845 */ - "tryRemove\0" /* 858 */ - "getFiniteTypes\0" /* 868 */ - "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 883 */ - "toPhpDocNode\0" /* 922 */ - "looseCompare"; /* 935 */ + "compare\0" /* 671 */ + "compareEachInteger\0" /* 679 */ + "getSmallerType\0" /* 698 */ + "getSmallerOrEqualType\0" /* 713 */ + "getGreaterType\0" /* 735 */ + "getGreaterOrEqualType\0" /* 750 */ + "PHPStan\\Type\\BooleanType\0" /* 772 */ + "toBoolean\0" /* 797 */ + "toAbsoluteNumber\0" /* 807 */ + "toBitwiseNotType\0" /* 824 */ + "toString\0" /* 841 */ + "tryUnion\0" /* 850 */ + "tryIntersect\0" /* 859 */ + "typeToRemove\0" /* 872 */ + "tryRemove\0" /* 885 */ + "getFiniteTypes\0" /* 895 */ + "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 910 */ + "toPhpDocNode\0" /* 949 */ + "looseCompare"; /* 962 */ 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 */ @@ -157,6 +159,10 @@ inline constexpr reg::PackedArg args[] = { 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(28, 0, 29), /* getSmallerType return */ reg::packed(546, 0, 557), /* getSmallerOrEqualType $phpVersion */ @@ -165,7 +171,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(28, 0, 29), /* getGreaterType return */ reg::packed(546, 0, 557), /* getGreaterOrEqualType $phpVersion */ reg::packed(28, 0, 29), /* getGreaterOrEqualType return */ - reg::packed(28, 0, 745), /* toBoolean return */ + reg::packed(28, 0, 772), /* toBoolean return */ reg::packed(28, 0, 29), /* toAbsoluteNumber return */ reg::packed(28, 0, 29), /* toBitwiseNotType return */ reg::packed(28, 0, 29), /* toString return */ @@ -173,13 +179,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(28, MAY_BE_NULL, 29), /* tryUnion return */ reg::packed(386, 0, 29), /* tryIntersect $otherType */ reg::packed(28, MAY_BE_NULL, 29), /* tryIntersect return */ - reg::packed(845, 0, 29), /* tryRemove $typeToRemove */ + reg::packed(872, 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, 883), /* toPhpDocNode return */ + reg::packed(28, 0, 910), /* toPhpDocNode return */ reg::packed(287, 0, 29), /* looseCompare $type */ reg::packed(546, 0, 557), /* looseCompare $phpVersion */ - reg::packed(28, 0, 745), /* looseCompare return */ + reg::packed(28, 0, 772), /* looseCompare return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -208,20 +214,21 @@ inline constexpr sigtab::Sig isSmallerThan = { { 601 /* isSmallerThan */, 2, 42, inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 615 /* isSmallerThanOrEqual */, 2, 45, 2, 47, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isGreaterThan = { { 636 /* isGreaterThan */, 2, 48, 2, 50, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig isGreaterThanOrEqual = { { 650 /* isGreaterThanOrEqual */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerType = { { 671 /* getSmallerType */, 1, 54, 1, 55, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSmallerOrEqualType = { { 686 /* getSmallerOrEqualType */, 1, 56, 1, 57, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterType = { { 708 /* getGreaterType */, 1, 58, 1, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getGreaterOrEqualType = { { 723 /* getGreaterOrEqualType */, 1, 60, 1, 61, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBoolean = { { 770 /* toBoolean */, 0, 62, 0, 62, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toAbsoluteNumber = { { 780 /* toAbsoluteNumber */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toBitwiseNotType = { { 797 /* toBitwiseNotType */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toString = { { 814 /* toString */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryUnion = { { 823 /* tryUnion */, 1, 66, 1, 67, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryIntersect = { { 832 /* tryIntersect */, 1, 68, 1, 69, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig tryRemove = { { 858 /* tryRemove */, 1, 70, 1, 71, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFiniteTypes = { { 868 /* getFiniteTypes */, 0, 72, 0, 72, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toPhpDocNode = { { 922 /* toPhpDocNode */, 0, 73, 0, 73, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig looseCompare = { { 935 /* looseCompare */, 2, 74, 2, 76, 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 toBoolean = { { 797 /* toBoolean */, 0, 66, 0, 66, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 807 /* toAbsoluteNumber */, 0, 67, 0, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 824 /* toBitwiseNotType */, 0, 68, 0, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 841 /* toString */, 0, 69, 0, 69, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryUnion = { { 850 /* tryUnion */, 1, 70, 1, 71, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryIntersect = { { 859 /* tryIntersect */, 1, 72, 1, 73, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig tryRemove = { { 885 /* tryRemove */, 1, 74, 1, 75, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFiniteTypes = { { 895 /* getFiniteTypes */, 0, 76, 0, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toPhpDocNode = { { 949 /* toPhpDocNode */, 0, 77, 0, 77, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 962 /* looseCompare */, 2, 78, 2, 80, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntegerRangeType diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 92c13f8125e..5d31efbb761 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -4496,6 +4496,7 @@ enum pt_php_version_query PT_PHP_VERSION_SUPPORTS_ASYMMETRIC_VISIBILITY, PT_PHP_VERSION_SUPPORTS_ENUMS, PT_PHP_VERSION_SUPPORTS_PROPERTY_HOOKS, + PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, PT_PHP_VERSION_QUERY_COUNT }; /* $phpVersion->() / ->getVersionId(): the $versionId slot of exactly diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index 9b20b44cc7a..ca6d88d9e33 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -214,6 +214,8 @@ 'string' => new \PHPStan\Type\StringType(), 'string0' => new \PHPStan\Type\Constant\ConstantStringType('0'), 'stringFoo' => new \PHPStan\Type\Constant\ConstantStringType('foo'), + 'string1abc' => new \PHPStan\Type\Constant\ConstantStringType('1abc'), + 'string1abcOr100' => new \PHPStan\Type\UnionType([new \PHPStan\Type\Constant\ConstantStringType('1abc'), new $constInt(100)]), 'numericString' => new \PHPStan\Type\IntersectionType([new \PHPStan\Type\StringType(), new \PHPStan\Type\Accessory\AccessoryNumericStringType()]), 'union' => new \PHPStan\Type\UnionType([new $constInt(1), new $constInt(2), new $constInt(3)]), 'unionMixed' => new \PHPStan\Type\UnionType([new $constInt(5), new \PHPStan\Type\NullType()]), From 90cf071e3ff668899406440ad1baa6d76e2cd2d8 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 20:32:18 +0000 Subject: [PATCH 11/14] Do not keep numeric-string for a concatenation that follows trailing whitespace PHP 8 allows trailing whitespace in numeric strings, so '1 ' is numeric but '1 ' . '5' is not. Only an integer, a decimal-int-string or constant strings not ending in whitespace (and any numeric-string on PHP 7) keep the result numeric. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 26 ++ tests/PHPStan/Analyser/nsrt/bcmath-number.php | 4 +- ...cat-numeric-string-trailing-whitespace.php | 24 ++ turbo-ext/src/IetrSupport.h | 1 + turbo-ext/src/OperatorTypeHelper.cpp | 65 +++- turbo-ext/src/PhpVersionAccess.cpp | 1 + turbo-ext/src/generated/OperatorTypeHelper.h | 309 +++++++++--------- turbo-ext/src/support.h | 1 + turbo-ext/tests/type-family.php | 2 +- 9 files changed, 272 insertions(+), 161 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/concat-numeric-string-trailing-whitespace.php diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 08cfd547805..a9f3fcd9906 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -131,6 +131,8 @@ public static function concat(Type $left, Type $right, PhpVersion $phpVersion): $leftNumericStringNonEmpty->isNumericString()->yes() // '' . '' is not numeric && $leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes() + // '1 ' is numeric on PHP 8, '1 5' is not + && !self::canEndInWhitespace($left, $leftStringType, $phpVersion) ) { // after the digits of an integer, a sign or a whitespace is not numeric, // and PHP 7 does not allow a trailing newline @@ -170,6 +172,30 @@ public static function concat(Type $left, Type $right, PhpVersion $phpVersion): return new StringType(); } + /** + * Whether the string a numeric left operand of . becomes can end in + * whitespace, which PHP 8 allows in numeric strings and PHP 7 does not. + */ + private static function canEndInWhitespace(Type $left, Type $leftStringType, PhpVersion $phpVersion): bool + { + if ($left->isInteger()->yes() || $left->isFloat()->yes() || $leftStringType->isDecimalIntegerString()->yes()) { + return false; + } + + $constantStrings = $leftStringType->getConstantStrings(); + if (count($constantStrings) > 0) { + foreach ($constantStrings as $constantString) { + if (Strings::match($constantString->getValue(), '#\\s\\z#') !== null) { + return true; + } + } + + return false; + } + + return $phpVersion->allowsTrailingWhitespaceInNumericStrings(); + } + public static function bitwiseAnd(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { if ($leftType instanceof NeverType || $rightType instanceof NeverType) { diff --git a/tests/PHPStan/Analyser/nsrt/bcmath-number.php b/tests/PHPStan/Analyser/nsrt/bcmath-number.php index 841c672d53a..a3e1860ae20 100644 --- a/tests/PHPStan/Analyser/nsrt/bcmath-number.php +++ b/tests/PHPStan/Analyser/nsrt/bcmath-number.php @@ -157,7 +157,7 @@ public function bcVsBool(Number $a, bool $b): void assertType('*ERROR*', $a * $b); assertType('*ERROR*', $a / $b); assertType('*ERROR*', $a % $b); - assertType('non-empty-string&numeric-string', $a . $b); + assertType('non-empty-string', $a . $b); assertType('*ERROR*', $a ** $b); assertType('*ERROR*', $a << $b); assertType('*ERROR*', $a >> $b); @@ -384,7 +384,7 @@ public function bcVsNever(Number $a): void assertType('*ERROR*', $a * $b); assertType('*ERROR*', $a / $b); assertType('*NEVER*', $a % $b); - assertType('non-empty-string&numeric-string', $a . $b); + assertType('non-empty-string', $a . $b); assertType('*ERROR*', $a ** $b); assertType('*NEVER*', $a << $b); assertType('*NEVER*', $a >> $b); diff --git a/tests/PHPStan/Analyser/nsrt/concat-numeric-string-trailing-whitespace.php b/tests/PHPStan/Analyser/nsrt/concat-numeric-string-trailing-whitespace.php new file mode 100644 index 00000000000..61e6de68369 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/concat-numeric-string-trailing-whitespace.php @@ -0,0 +1,24 @@ += 8.0 + +namespace ConcatNumericStringTrailingWhitespace; + +use function PHPStan\Testing\assertType; + +/** + * @param numeric-string $numericString + * @param decimal-int-string $decimalIntString + * @param int<0, max> $nonNegative + */ +function doFoo(string $numericString, string $decimalIntString, int $int, float $float, $nonNegative): void +{ + // PHP 8 allows trailing whitespace in numeric strings: '1 ' . '5' is not numeric + assertType('non-falsy-string', $numericString . '5'); + assertType('non-falsy-string', $numericString . $nonNegative); + assertType('lowercase-string&non-falsy-string&uppercase-string', '1 ' . $nonNegative); + + // integers, floats and decimal-int-strings never end in whitespace + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $decimalIntString . '5'); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $int . $nonNegative); + assertType('non-falsy-string&numeric-string&uppercase-string', $float . $nonNegative); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', '15' . $nonNegative); +} diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h index 09020c899ad..dfddc48b34a 100644 --- a/turbo-ext/src/IetrSupport.h +++ b/turbo-ext/src/IetrSupport.h @@ -84,6 +84,7 @@ inline constexpr Method mGetConstantScalarTypes{"getConstantScalarTypes"}; inline constexpr Method mGetConstantScalarValues{"getConstantScalarValues", PT_OP_GET_CONSTANT_SCALAR_VALUES}; inline constexpr Method mGetFiniteTypes{"getFiniteTypes"}; inline constexpr Method mGetConstantStrings{"getConstantStrings"}; +inline constexpr Method mIsDecimalIntegerString{"isDecimalIntegerString"}; inline constexpr Method mGetConstantArrays{"getConstantArrays", PT_OP_GET_CONSTANT_ARRAYS}; inline constexpr Method mIsInteger{"isInteger", PT_OP_IS_INTEGER}; inline constexpr Method mIsString{"isString", PT_OP_IS_STRING}; diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 3adb97006d8..b94c7a05a78 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -11,9 +11,9 @@ * arithmetic runs on the engine's own operator functions (add_function(), * mod_function(), bitwise_and_function(), zend_compare(), ...), so int * overflow into float, float-to-int truncation and the mixed-type - * comparisons behave exactly like the twin's PHP operators. None of the - * operators depends on the PhpVersion yet; the twin's parameter is accepted - * and passed along. + * comparisons behave exactly like the twin's PHP operators. Only concat() + * reads the PhpVersion so far (trailing whitespace in numeric strings); the + * other operators accept it and pass it along. */ #include "support.h" @@ -43,7 +43,7 @@ class OperatorTypeHelper /* {{{ concat() */ /* Mirrors concat(). */ - static zv::Val concat(zval *left, zval *right) + static zv::Val concat(zval *left, zval *right, zval *phpVersion) { IETR_VAL(leftStringType, callOn(left, mToString)); IETR_VAL(rightStringType, callOn(right, mToString)); @@ -177,6 +177,12 @@ class OperatorTypeHelper IETR_TRI(rightNonEmpty, triOn(rightStringType.raw(), mIsNonEmptyString)); numericResult = pt_trinary_or(leftNonEmpty, rightNonEmpty) == PT_TRI_YES; } + if (numericResult) { + /* '1 ' is numeric on PHP 8, '1 5' is not */ + int endsInWhitespace = canEndInWhitespace(left, leftStringType.raw(), phpVersion); + if (UNEXPECTED(endsInWhitespace < 0)) return zv::Val(); + numericResult = endsInWhitespace == 0; + } if (numericResult) { IETR_TRI(leftIsInteger, triOn(left, mIsInteger)); bool integerValidation = leftIsInteger == PT_TRI_YES; @@ -405,6 +411,44 @@ class OperatorTypeHelper return true; } + /* Mirrors canEndInWhitespace(): 1 / 0, -1 = pending exception */ + static int canEndInWhitespace(zval *left, zval *leftStringType, zval *phpVersion) + { + zend_long leftIsInteger = triOn(left, mIsInteger); + if (UNEXPECTED(leftIsInteger < 0)) return -1; + if (leftIsInteger == PT_TRI_YES) return 0; + zend_long leftIsFloat = triOn(left, mIsFloat); + if (UNEXPECTED(leftIsFloat < 0)) return -1; + if (leftIsFloat == PT_TRI_YES) return 0; + zend_long leftIsDecimal = triOn(leftStringType, mIsDecimalIntegerString); + if (UNEXPECTED(leftIsDecimal < 0)) return -1; + if (leftIsDecimal == PT_TRI_YES) return 0; + + zv::Val constantStrings = callOn(leftStringType, mGetConstantStrings); + if (UNEXPECTED(constantStrings.isUndef()) || UNEXPECTED(!requireArray(constantStrings.raw()))) return -1; + if (zend_hash_num_elements(Z_ARRVAL_P(constantStrings.raw())) > 0) { + for (zv::ArrayEntry entry : zv::ArrRef(constantStrings.raw())) { + zv::Val value = constantStringValue(entry.value().raw()); + if (UNEXPECTED(value.isUndef())) return -1; + if (UNEXPECTED(Z_TYPE_P(value.raw()) != IS_STRING)) { + pt_throw_should_not_happen(); + return -1; + } + /* Strings::match($value, '#\s\z#'): PCRE's \s */ + size_t length = Z_STRLEN_P(value.raw()); + if (length > 0) { + char last = Z_STRVAL_P(value.raw())[length - 1]; + if (last == ' ' || last == '\t' || last == '\n' || last == '\v' || last == '\f' || last == '\r') return 1; + } + } + return 0; + } + + bool allows; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS, allows))) return -1; + return allows ? 1 : 0; + } + /* $operationCallable($a, $b) or the operator */ static zv::Val applyOperation(pt_ietr_binary_op operation, zval *operationCallable, zval *a, zval *b) { @@ -2079,7 +2123,6 @@ using phpstanturbo::NullableLong; zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion) { - (void) phpVersion; switch (op) { case PT_OPERATOR_ADD: return OperatorTypeHelper::add(left, right); @@ -2104,7 +2147,7 @@ zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval case PT_OPERATOR_SHIFT_RIGHT: return OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, left, right); case PT_OPERATOR_CONCATENATE: - return OperatorTypeHelper::concat(left, right); + return OperatorTypeHelper::concat(left, right, phpVersion); case PT_OPERATOR_ARRAY_UNION: return OperatorTypeHelper::arrayUnion(left, right); } @@ -2207,7 +2250,7 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) cls.privateClassConstantLong("IS_UNKNOWN", PT_IETR_IS_UNKNOWN); ptdecl::OperatorTypeHelper::declareProperties(cls); - cls.method(sigs::concat, OT_BINARY_GLUE(OperatorTypeHelper::concat(leftType, rightType))); + cls.method(sigs::concat, OT_BINARY_GLUE(OperatorTypeHelper::concat(leftType, rightType, phpVersion))); cls.method(sigs::bitwiseAnd, OT_BINARY_GLUE(OperatorTypeHelper::bitwise(IETR_BITWISE_AND, leftType, rightType))); cls.method(sigs::bitwiseOr, OT_BINARY_GLUE(OperatorTypeHelper::bitwise(IETR_BITWISE_OR, leftType, rightType))); cls.method(sigs::bitwiseXor, OT_BINARY_GLUE(OperatorTypeHelper::bitwise(IETR_BITWISE_XOR, leftType, rightType))); @@ -2253,6 +2296,14 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) 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::optimizeScalarType, OT_TYPE_GLUE(OperatorTypeHelper::optimizeScalarType(type))); + cls.method(sigs::canEndInWhitespace, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *left, *leftStringType, *phpVersion; + if (!zp::parse(execute_data, left, leftStringType, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!otArgType(left, 1) || !otArgType(leftStringType, 2) || !otArgPhpVersion(phpVersion, 3))) RETURN_THROWS(); + int result = OperatorTypeHelper::canEndInWhitespace(left, leftStringType, phpVersion); + if (UNEXPECTED(result < 0)) RETURN_THROWS(); + RETURN_BOOL(result == 1); + }); cls.method(sigs::getNonNegativeIntegerBounds, OT_TYPE_GLUE(OperatorTypeHelper::getNonNegativeIntegerBoundsValue(type))); cls.method(sigs::computeBitwiseAndRange, OT_TYPES_GLUE(OperatorTypeHelper::computeBitwiseAndRange(leftType, rightType))); cls.method(sigs::computeBitwiseOrXorRange, OT_TYPES_GLUE(OperatorTypeHelper::computeBitwiseOrXorRange(leftType, rightType))); diff --git a/turbo-ext/src/PhpVersionAccess.cpp b/turbo-ext/src/PhpVersionAccess.cpp index 809d462b80a..1dac7a7b2d2 100644 --- a/turbo-ext/src/PhpVersionAccess.cpp +++ b/turbo-ext/src/PhpVersionAccess.cpp @@ -40,6 +40,7 @@ const VersionQuery pt_pva_queries[PT_PHP_VERSION_QUERY_COUNT] = { /* PT_PHP_VERSION_SUPPORTS_ENUMS */ {PT_LC("supportsenums"), "supportsEnums", 80100, false}, /* PT_PHP_VERSION_SUPPORTS_PROPERTY_HOOKS */ {PT_LC("supportspropertyhooks"), "supportsPropertyHooks", 80400, false}, /* PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON */ {PT_LC("castsnumberstostringsonloosecomparison"), "castsNumbersToStringsOnLooseComparison", 80000, false}, + /* PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS */ {PT_LC("allowstrailingwhitespaceinnumericstrings"), "allowsTrailingWhitespaceInNumericStrings", 80000, false}, }; pt_method_site pt_pva_query_sites[PT_PHP_VERSION_QUERY_COUNT]; diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index 764b24ba7de..5da59910828 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -29,174 +29,180 @@ inline constexpr char strings[] = "PHPStan\\Php\\PhpVersion\0" /* 40 */ "\0" /* 63 */ "concat\0" /* 64 */ - "leftType\0" /* 71 */ - "rightType\0" /* 80 */ - "bitwiseAnd\0" /* 90 */ - "bitwiseOr\0" /* 101 */ - "bitwiseXor\0" /* 111 */ - "operationCallable\0" /* 122 */ - "getFiniteOrConstantScalarTypes\0" /* 140 */ - "divide\0" /* 171 */ - "getDivTypeFromTypes\0" /* 178 */ - "modulo\0" /* 198 */ - "divisorMin\0" /* 205 */ - "divisorMax\0" /* 216 */ - "getMaxModuloMagnitude\0" /* 227 */ - "type\0" /* 249 */ - "getIntegerBounds\0" /* 254 */ - "add\0" /* 271 */ - "arrayUnion\0" /* 275 */ - "tryArrayUnion\0" /* 286 */ - "subtract\0" /* 300 */ - "multiply\0" /* 309 */ - "shiftLeft\0" /* 318 */ - "shiftRight\0" /* 328 */ - "optimizeScalarType\0" /* 339 */ - "getNonNegativeIntegerBounds\0" /* 358 */ - "leftNumberType\0" /* 386 */ - "rightNumberType\0" /* 401 */ - "computeBitwiseAndRange\0" /* 417 */ - "computeBitwiseOrXorRange\0" /* 440 */ - "value\0" /* 465 */ - "allBitsMask\0" /* 471 */ - "operator\0" /* 483 */ - "resolveCommonMath\0" /* 492 */ - "range\0" /* 510 */ - "operand\0" /* 516 */ - "integerRangeMath\0" /* 524 */ - "shift\0" /* 541 */ - "shiftLeftOverflows\0" /* 547 */ - "negateNumber\0" /* 566 */ - "toIntBound\0" /* 579 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 590 */ - "toIntegerOperand\0" /* 622 */ - "getTypeFromValue\0" /* 639 */ - "getNeverType\0" /* 656 */ - "negate\0" /* 669 */ - "numeratorType\0" /* 676 */ - "divisorType\0" /* 690 */ - "intDivide\0" /* 702 */ - "getIntervals\0" /* 712 */ - "numeratorMin\0" /* 725 */ - "numeratorMax\0" /* 738 */ - "divideIntervals\0" /* 751 */ - "numerator\0" /* 767 */ - "divisor\0" /* 777 */ - "divideBounds"; /* 785 */ + "leftStringType\0" /* 71 */ + "canEndInWhitespace\0" /* 86 */ + "leftType\0" /* 105 */ + "rightType\0" /* 114 */ + "bitwiseAnd\0" /* 124 */ + "bitwiseOr\0" /* 135 */ + "bitwiseXor\0" /* 145 */ + "operationCallable\0" /* 156 */ + "getFiniteOrConstantScalarTypes\0" /* 174 */ + "divide\0" /* 205 */ + "getDivTypeFromTypes\0" /* 212 */ + "modulo\0" /* 232 */ + "divisorMin\0" /* 239 */ + "divisorMax\0" /* 250 */ + "getMaxModuloMagnitude\0" /* 261 */ + "type\0" /* 283 */ + "getIntegerBounds\0" /* 288 */ + "add\0" /* 305 */ + "arrayUnion\0" /* 309 */ + "tryArrayUnion\0" /* 320 */ + "subtract\0" /* 334 */ + "multiply\0" /* 343 */ + "shiftLeft\0" /* 352 */ + "shiftRight\0" /* 362 */ + "optimizeScalarType\0" /* 373 */ + "getNonNegativeIntegerBounds\0" /* 392 */ + "leftNumberType\0" /* 420 */ + "rightNumberType\0" /* 435 */ + "computeBitwiseAndRange\0" /* 451 */ + "computeBitwiseOrXorRange\0" /* 474 */ + "value\0" /* 499 */ + "allBitsMask\0" /* 505 */ + "operator\0" /* 517 */ + "resolveCommonMath\0" /* 526 */ + "range\0" /* 544 */ + "operand\0" /* 550 */ + "integerRangeMath\0" /* 558 */ + "shift\0" /* 575 */ + "shiftLeftOverflows\0" /* 581 */ + "negateNumber\0" /* 600 */ + "toIntBound\0" /* 613 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 624 */ + "toIntegerOperand\0" /* 656 */ + "getTypeFromValue\0" /* 673 */ + "getNeverType\0" /* 690 */ + "negate\0" /* 703 */ + "numeratorType\0" /* 710 */ + "divisorType\0" /* 724 */ + "intDivide\0" /* 736 */ + "getIntervals\0" /* 746 */ + "numeratorMin\0" /* 759 */ + "numeratorMax\0" /* 772 */ + "divideIntervals\0" /* 785 */ + "numerator\0" /* 801 */ + "divisor\0" /* 811 */ + "divideBounds"; /* 819 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* concat $left */ reg::packed(23, 0, 5), /* concat $right */ reg::packed(29, 0, 40), /* concat $phpVersion */ reg::packed(63, 0, 5), /* concat return */ - reg::packed(71, 0, 5), /* bitwiseAnd $leftType */ - reg::packed(80, 0, 5), /* bitwiseAnd $rightType */ + reg::packed(0, 0, 5), /* canEndInWhitespace $left */ + reg::packed(71, 0, 5), /* canEndInWhitespace $leftStringType */ + reg::packed(29, 0, 40), /* canEndInWhitespace $phpVersion */ + reg::packed(63, MAY_BE_BOOL), /* canEndInWhitespace return */ + reg::packed(105, 0, 5), /* bitwiseAnd $leftType */ + reg::packed(114, 0, 5), /* bitwiseAnd $rightType */ reg::packed(29, 0, 40), /* bitwiseAnd $phpVersion */ reg::packed(63, 0, 5), /* bitwiseAnd return */ - reg::packed(71, 0, 5), /* bitwiseOr $leftType */ - reg::packed(80, 0, 5), /* bitwiseOr $rightType */ + reg::packed(105, 0, 5), /* bitwiseOr $leftType */ + reg::packed(114, 0, 5), /* bitwiseOr $rightType */ reg::packed(29, 0, 40), /* bitwiseOr $phpVersion */ reg::packed(63, 0, 5), /* bitwiseOr return */ - reg::packed(71, 0, 5), /* bitwiseXor $leftType */ - reg::packed(80, 0, 5), /* bitwiseXor $rightType */ + reg::packed(105, 0, 5), /* bitwiseXor $leftType */ + reg::packed(114, 0, 5), /* bitwiseXor $rightType */ reg::packed(29, 0, 40), /* bitwiseXor $phpVersion */ reg::packed(63, 0, 5), /* bitwiseXor return */ - reg::packed(71, 0, 5), /* getFiniteOrConstantScalarTypes $leftType */ - reg::packed(80, 0, 5), /* getFiniteOrConstantScalarTypes $rightType */ - reg::packed(122, MAY_BE_CALLABLE), /* getFiniteOrConstantScalarTypes $operationCallable */ + reg::packed(105, 0, 5), /* getFiniteOrConstantScalarTypes $leftType */ + reg::packed(114, 0, 5), /* getFiniteOrConstantScalarTypes $rightType */ + reg::packed(156, MAY_BE_CALLABLE), /* getFiniteOrConstantScalarTypes $operationCallable */ reg::packed(63, MAY_BE_LONG, 5), /* getFiniteOrConstantScalarTypes return */ - reg::packed(71, 0, 5), /* divide $leftType */ - reg::packed(80, 0, 5), /* divide $rightType */ + reg::packed(105, 0, 5), /* divide $leftType */ + reg::packed(114, 0, 5), /* divide $rightType */ reg::packed(29, 0, 40), /* divide $phpVersion */ reg::packed(63, 0, 5), /* divide return */ - reg::packed(71, 0, 5), /* getDivTypeFromTypes $leftType */ - reg::packed(80, 0, 5), /* getDivTypeFromTypes $rightType */ + reg::packed(105, 0, 5), /* getDivTypeFromTypes $leftType */ + reg::packed(114, 0, 5), /* getDivTypeFromTypes $rightType */ reg::packed(63, 0, 5), /* getDivTypeFromTypes return */ - reg::packed(71, 0, 5), /* modulo $leftType */ - reg::packed(80, 0, 5), /* modulo $rightType */ + 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(205, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ - reg::packed(216, 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(249, 0, 5), /* getIntegerBounds $type */ + reg::packed(283, 0, 5), /* getIntegerBounds $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerBounds return */ - reg::packed(71, 0, 5), /* add $leftType */ - reg::packed(80, 0, 5), /* add $rightType */ + reg::packed(105, 0, 5), /* add $leftType */ + reg::packed(114, 0, 5), /* add $rightType */ reg::packed(29, 0, 40), /* add $phpVersion */ reg::packed(63, 0, 5), /* add return */ - reg::packed(71, 0, 5), /* arrayUnion $leftType */ - reg::packed(80, 0, 5), /* arrayUnion $rightType */ + reg::packed(105, 0, 5), /* arrayUnion $leftType */ + reg::packed(114, 0, 5), /* arrayUnion $rightType */ reg::packed(29, 0, 40), /* arrayUnion $phpVersion */ reg::packed(63, 0, 5), /* arrayUnion return */ - reg::packed(71, 0, 5), /* tryArrayUnion $leftType */ - reg::packed(80, 0, 5), /* tryArrayUnion $rightType */ + reg::packed(105, 0, 5), /* tryArrayUnion $leftType */ + reg::packed(114, 0, 5), /* tryArrayUnion $rightType */ reg::packed(63, MAY_BE_NULL, 5), /* tryArrayUnion return */ - reg::packed(71, 0, 5), /* subtract $leftType */ - reg::packed(80, 0, 5), /* subtract $rightType */ + reg::packed(105, 0, 5), /* subtract $leftType */ + reg::packed(114, 0, 5), /* subtract $rightType */ reg::packed(29, 0, 40), /* subtract $phpVersion */ reg::packed(63, 0, 5), /* subtract return */ - reg::packed(71, 0, 5), /* multiply $leftType */ - reg::packed(80, 0, 5), /* multiply $rightType */ + reg::packed(105, 0, 5), /* multiply $leftType */ + reg::packed(114, 0, 5), /* multiply $rightType */ reg::packed(29, 0, 40), /* multiply $phpVersion */ reg::packed(63, 0, 5), /* multiply return */ - reg::packed(71, 0, 5), /* shiftLeft $leftType */ - reg::packed(80, 0, 5), /* shiftLeft $rightType */ + reg::packed(105, 0, 5), /* shiftLeft $leftType */ + reg::packed(114, 0, 5), /* shiftLeft $rightType */ reg::packed(29, 0, 40), /* shiftLeft $phpVersion */ reg::packed(63, 0, 5), /* shiftLeft return */ - reg::packed(71, 0, 5), /* shiftRight $leftType */ - reg::packed(80, 0, 5), /* shiftRight $rightType */ + reg::packed(105, 0, 5), /* shiftRight $leftType */ + reg::packed(114, 0, 5), /* shiftRight $rightType */ reg::packed(29, 0, 40), /* shiftRight $phpVersion */ reg::packed(63, 0, 5), /* shiftRight return */ - reg::packed(249, 0, 5), /* optimizeScalarType $type */ + reg::packed(283, 0, 5), /* optimizeScalarType $type */ reg::packed(63, 0, 5), /* optimizeScalarType return */ - reg::packed(249, 0, 5), /* getNonNegativeIntegerBounds $type */ + reg::packed(283, 0, 5), /* getNonNegativeIntegerBounds $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ - reg::packed(386, 0, 5), /* computeBitwiseAndRange $leftNumberType */ - reg::packed(401, 0, 5), /* computeBitwiseAndRange $rightNumberType */ + reg::packed(420, 0, 5), /* computeBitwiseAndRange $leftNumberType */ + reg::packed(435, 0, 5), /* computeBitwiseAndRange $rightNumberType */ reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseAndRange return */ - reg::packed(386, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ - reg::packed(401, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ + reg::packed(420, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ + reg::packed(435, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseOrXorRange return */ - reg::packed(465, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(499, MAY_BE_LONG), /* allBitsMask $value */ reg::packed(63, MAY_BE_LONG), /* allBitsMask return */ - reg::packed(483, MAY_BE_STRING), /* resolveCommonMath $operator */ - reg::packed(71, 0, 5), /* resolveCommonMath $leftType */ - reg::packed(80, 0, 5), /* resolveCommonMath $rightType */ + reg::packed(517, 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(510, 0, 5), /* integerRangeMath $range */ - reg::packed(483, MAY_BE_STRING), /* integerRangeMath $operator */ - reg::packed(516, 0, 5), /* integerRangeMath $operand */ + reg::packed(544, 0, 5), /* integerRangeMath $range */ + reg::packed(517, MAY_BE_STRING), /* integerRangeMath $operator */ + reg::packed(550, 0, 5), /* integerRangeMath $operand */ reg::packed(63, 0, 5), /* integerRangeMath return */ - reg::packed(465, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(541, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(499, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(575, MAY_BE_LONG), /* shiftLeftOverflows $shift */ reg::packed(63, MAY_BE_BOOL), /* shiftLeftOverflows return */ - reg::packed(249, 0, 5), /* negateNumber $type */ + reg::packed(283, 0, 5), /* negateNumber $type */ reg::packed(63, 0, 5), /* negateNumber return */ - reg::packed(465, MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(499, MAY_BE_DOUBLE), /* toIntBound $value */ reg::packed(63, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ - reg::packed(249, 0, 590), /* toIntegerOperand $type */ + reg::packed(283, 0, 624), /* toIntegerOperand $type */ reg::packed(63, MAY_BE_LONG), /* toIntegerOperand return */ - reg::packed(465, 0), /* getTypeFromValue $value */ + reg::packed(499, 0), /* getTypeFromValue $value */ reg::packed(63, 0, 5), /* getTypeFromValue return */ - reg::packed(71, 0, 5), /* getNeverType $leftType */ - reg::packed(80, 0, 5), /* getNeverType $rightType */ + reg::packed(105, 0, 5), /* getNeverType $leftType */ + reg::packed(114, 0, 5), /* getNeverType $rightType */ reg::packed(63, 0, 5), /* getNeverType return */ - reg::packed(249, 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(676, 0, 5), /* intDivide $numeratorType */ - reg::packed(690, 0, 5), /* intDivide $divisorType */ + reg::packed(710, 0, 5), /* intDivide $numeratorType */ + reg::packed(724, 0, 5), /* intDivide $divisorType */ reg::packed(29, 0, 40), /* intDivide $phpVersion */ reg::packed(63, 0, 5), /* intDivide return */ - reg::packed(249, 0, 5), /* getIntervals $type */ + reg::packed(283, 0, 5), /* getIntervals $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntervals return */ - reg::packed(725, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ - reg::packed(738, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ - reg::packed(205, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ - reg::packed(216, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ + reg::packed(759, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ + reg::packed(772, 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(767, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ - reg::packed(777, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ + reg::packed(801, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ + reg::packed(811, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ reg::packed(63, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds return */ }; using Sig = reg::Sig; @@ -205,40 +211,41 @@ 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 concat = { { 64 /* concat */, 3, 0, 3, 3, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig bitwiseAnd = { { 90 /* bitwiseAnd */, 3, 4, 3, 7, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig bitwiseOr = { { 101 /* bitwiseOr */, 3, 8, 3, 11, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig bitwiseXor = { { 111 /* bitwiseXor */, 3, 12, 3, 15, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFiniteOrConstantScalarTypes = { { 140 /* getFiniteOrConstantScalarTypes */, 3, 16, 3, 19, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divide = { { 171 /* divide */, 3, 20, 3, 23, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getDivTypeFromTypes = { { 178 /* getDivTypeFromTypes */, 2, 24, 2, 26, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig modulo = { { 198 /* modulo */, 3, 27, 3, 30, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 227 /* getMaxModuloMagnitude */, 2, 31, 2, 33, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerBounds = { { 254 /* getIntegerBounds */, 1, 34, 1, 35, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig add = { { 271 /* add */, 3, 36, 3, 39, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 275 /* arrayUnion */, 3, 40, 3, 43, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 286 /* tryArrayUnion */, 2, 44, 2, 46, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig subtract = { { 300 /* subtract */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig multiply = { { 309 /* multiply */, 3, 51, 3, 54, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 318 /* shiftLeft */, 3, 55, 3, 58, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftRight = { { 328 /* shiftRight */, 3, 59, 3, 62, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 339 /* optimizeScalarType */, 1, 63, 1, 64, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 358 /* getNonNegativeIntegerBounds */, 1, 65, 1, 66, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseAndRange = { { 417 /* computeBitwiseAndRange */, 2, 67, 2, 69, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 440 /* computeBitwiseOrXorRange */, 2, 70, 2, 72, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig allBitsMask = { { 471 /* allBitsMask */, 1, 73, 1, 74, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 492 /* resolveCommonMath */, 3, 75, 3, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig integerRangeMath = { { 524 /* integerRangeMath */, 3, 79, 3, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 547 /* shiftLeftOverflows */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negateNumber = { { 566 /* negateNumber */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntBound = { { 579 /* toIntBound */, 1, 88, 1, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntegerOperand = { { 622 /* toIntegerOperand */, 1, 90, 1, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 639 /* getTypeFromValue */, 1, 92, 1, 93, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 656 /* getNeverType */, 2, 94, 2, 96, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 669 /* negate */, 2, 97, 2, 99, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 702 /* intDivide */, 3, 100, 3, 103, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntervals = { { 712 /* getIntervals */, 1, 104, 1, 105, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntervals = { { 751 /* divideIntervals */, 4, 106, 4, 110, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBounds = { { 785 /* divideBounds */, 2, 111, 2, 113, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canEndInWhitespace = { { 86 /* canEndInWhitespace */, 3, 4, 3, 7, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 124 /* bitwiseAnd */, 3, 8, 3, 11, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 135 /* bitwiseOr */, 3, 12, 3, 15, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +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 getDivTypeFromTypes = { { 212 /* getDivTypeFromTypes */, 2, 28, 2, 30, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig modulo = { { 232 /* modulo */, 3, 31, 3, 34, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 261 /* getMaxModuloMagnitude */, 2, 35, 2, 37, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerBounds = { { 288 /* getIntegerBounds */, 1, 38, 1, 39, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig add = { { 305 /* add */, 3, 40, 3, 43, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 309 /* arrayUnion */, 3, 44, 3, 47, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 320 /* tryArrayUnion */, 2, 48, 2, 50, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig subtract = { { 334 /* subtract */, 3, 51, 3, 54, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig multiply = { { 343 /* multiply */, 3, 55, 3, 58, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 352 /* shiftLeft */, 3, 59, 3, 62, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftRight = { { 362 /* shiftRight */, 3, 63, 3, 66, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig optimizeScalarType = { { 373 /* optimizeScalarType */, 1, 67, 1, 68, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 392 /* getNonNegativeIntegerBounds */, 1, 69, 1, 70, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseAndRange = { { 451 /* computeBitwiseAndRange */, 2, 71, 2, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 474 /* computeBitwiseOrXorRange */, 2, 74, 2, 76, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig allBitsMask = { { 505 /* allBitsMask */, 1, 77, 1, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolveCommonMath = { { 526 /* resolveCommonMath */, 3, 79, 3, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig integerRangeMath = { { 558 /* integerRangeMath */, 3, 83, 3, 86, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 581 /* shiftLeftOverflows */, 2, 87, 2, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negateNumber = { { 600 /* negateNumber */, 1, 90, 1, 91, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 613 /* toIntBound */, 1, 92, 1, 93, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntegerOperand = { { 656 /* toIntegerOperand */, 1, 94, 1, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 673 /* getTypeFromValue */, 1, 96, 1, 97, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNeverType = { { 690 /* getNeverType */, 2, 98, 2, 100, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negate = { { 703 /* negate */, 2, 101, 2, 103, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 736 /* intDivide */, 3, 104, 3, 107, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntervals = { { 746 /* getIntervals */, 1, 108, 1, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntervals = { { 785 /* divideIntervals */, 4, 110, 4, 114, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBounds = { { 819 /* divideBounds */, 2, 115, 2, 117, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 5d31efbb761..08a1d65da76 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -4497,6 +4497,7 @@ enum pt_php_version_query PT_PHP_VERSION_SUPPORTS_ENUMS, PT_PHP_VERSION_SUPPORTS_PROPERTY_HOOKS, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, + PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS, PT_PHP_VERSION_QUERY_COUNT }; /* $phpVersion->() / ->getVersionId(): the $versionId slot of exactly diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index ca6d88d9e33..cda5f8004df 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7799,7 +7799,7 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu 'mod' => '%', ]; $integerOperands = ['int0' => $int(0), 'int7' => $int(7), 'int-3' => $int(-3), 'intMax' => $int(PHP_INT_MAX), 'intMin' => $int(PHP_INT_MIN), 'range0-10' => $range(0, 10), 'rangeMin--1' => $range(null, -1), 'range5-max' => $range(5, null), 'range-4-6' => $range(-4, 6), 'rangeMin-max' => $range(-1000, null), 'unionRangeConst' => $operands['unionRangeConst'], 'benevolentRanges' => new \PHPStan\Type\BenevolentUnionType([$range(0, 3), $int(-8)])]; - $numericStringOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809'), 'leadingSpace' => new \PHPStan\Type\Constant\ConstantStringType(' 1'), 'plusSign' => new \PHPStan\Type\Constant\ConstantStringType('+5'), 'trailingNewline' => new \PHPStan\Type\Constant\ConstantStringType("5\n"), 'exponent' => new \PHPStan\Type\Constant\ConstantStringType('1.5e3')]; + $numericStringOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809'), 'leadingSpace' => new \PHPStan\Type\Constant\ConstantStringType(' 1'), 'plusSign' => new \PHPStan\Type\Constant\ConstantStringType('+5'), 'trailingNewline' => new \PHPStan\Type\Constant\ConstantStringType("5\n"), 'exponent' => new \PHPStan\Type\Constant\ConstantStringType('1.5e3'), 'trailingSpace' => new \PHPStan\Type\Constant\ConstantStringType('1 '), 'decimalIntString' => new \PHPStan\Type\IntersectionType([new \PHPStan\Type\StringType(), new \PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType()])]; foreach ($integerOperands + $numericStringOperands as $name => $operand) { foreach ($operand->getConstantScalarTypes() as $i => $constant) { $r["toIntegerOperand $name $i"] = $catching(static fn () => $staticPrivate('toIntegerOperand', $constant)); From f48583045466cbe9236644101f7a405b6bb6f970 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 20:38:35 +0000 Subject: [PATCH 12/14] Infer an error for an intersection raised to an invalid exponent A member's ErrorType is a MixedType, so intersecting it with the other members' powers hid it: numeric-string ** 'abc' was float|int although it always throws a TypeError. An erroring member now decides for the intersection. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/IntersectionType.php | 13 ++++++++++- .../Analyser/nsrt/power-accessory-string.php | 23 +++++++++++++++++++ turbo-ext/src/IntersectionType.cpp | 17 +++++++++++++- turbo-ext/tests/type-family.php | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/power-accessory-string.php diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index e158ce989d5..c6dcdb585e3 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1776,7 +1776,18 @@ public function tryRemove(Type $typeToRemove): ?Type public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->arithmeticPower($exponent, $phpVersion)); + // a member that errors decides for the value; intersecting an ErrorType, + // a MixedType, with the other members' results would hide it + $results = []; + foreach ($this->types as $type) { + $result = $type->arithmeticPower($exponent, $phpVersion); + if ($result instanceof ErrorType) { + return $result; + } + $results[] = $result; + } + + return self::intersectOperands($results); } public function getFiniteTypes(): array diff --git a/tests/PHPStan/Analyser/nsrt/power-accessory-string.php b/tests/PHPStan/Analyser/nsrt/power-accessory-string.php new file mode 100644 index 00000000000..c346441cff1 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/power-accessory-string.php @@ -0,0 +1,23 @@ +types(); + if (UNEXPECTED(types == NULL)) return zv::Val(); + zv::Val typesCopy = zv::Val::copyOf(zv::Ref(types)); zv::Args args{exponent, phpVersion}; - return intersectCall(PT_LC("arithmeticpower"), 2, args); + zv::Arr results = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(typesCopy.raw()))); + for (zv::ArrayEntry entry : zv::ArrRef(typesCopy.raw())) { + zv::Val result = callType(entry.value().deref().asObject(), PT_LC("arithmeticpower"), 2, args); + if (UNEXPECTED(result.isUndef())) return zv::Val(); + if (Z_TYPE_P(result.raw()) == IS_OBJECT && instanceof_function(Z_OBJCE_P(result.raw()), pt_ce_error_type)) { + return result; + } + results.push(std::move(result)); + } + return intersectOperands(results.arrRef()); } /* the finite types every member has, keyed by enum case or type-only diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index cda5f8004df..c3912a633e9 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7799,7 +7799,7 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu 'mod' => '%', ]; $integerOperands = ['int0' => $int(0), 'int7' => $int(7), 'int-3' => $int(-3), 'intMax' => $int(PHP_INT_MAX), 'intMin' => $int(PHP_INT_MIN), 'range0-10' => $range(0, 10), 'rangeMin--1' => $range(null, -1), 'range5-max' => $range(5, null), 'range-4-6' => $range(-4, 6), 'rangeMin-max' => $range(-1000, null), 'unionRangeConst' => $operands['unionRangeConst'], 'benevolentRanges' => new \PHPStan\Type\BenevolentUnionType([$range(0, 3), $int(-8)])]; - $numericStringOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809'), 'leadingSpace' => new \PHPStan\Type\Constant\ConstantStringType(' 1'), 'plusSign' => new \PHPStan\Type\Constant\ConstantStringType('+5'), 'trailingNewline' => new \PHPStan\Type\Constant\ConstantStringType("5\n"), 'exponent' => new \PHPStan\Type\Constant\ConstantStringType('1.5e3'), 'trailingSpace' => new \PHPStan\Type\Constant\ConstantStringType('1 '), 'decimalIntString' => new \PHPStan\Type\IntersectionType([new \PHPStan\Type\StringType(), new \PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType()])]; + $numericStringOperands = ['overflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('9223372036854775808'), 'negativeOverflowingNumericString' => new \PHPStan\Type\Constant\ConstantStringType('-9223372036854775809'), 'leadingSpace' => new \PHPStan\Type\Constant\ConstantStringType(' 1'), 'plusSign' => new \PHPStan\Type\Constant\ConstantStringType('+5'), 'trailingNewline' => new \PHPStan\Type\Constant\ConstantStringType("5\n"), 'exponent' => new \PHPStan\Type\Constant\ConstantStringType('1.5e3'), 'trailingSpace' => new \PHPStan\Type\Constant\ConstantStringType('1 '), 'nonNumericString' => new \PHPStan\Type\Constant\ConstantStringType('abc'), 'decimalIntString' => new \PHPStan\Type\IntersectionType([new \PHPStan\Type\StringType(), new \PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType()])]; foreach ($integerOperands + $numericStringOperands as $name => $operand) { foreach ($operand->getConstantScalarTypes() as $i => $constant) { $r["toIntegerOperand $name $i"] = $catching(static fn () => $staticPrivate('toIntegerOperand', $constant)); From d9fc04e3e0e9f20f6cfbf13e2aab8293b514b4a7 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 21:27:36 +0000 Subject: [PATCH 13/14] Ignore the PhpVersion injection in IntdivFunctionReturnTypeExtension in the config PHP 7.4 reads the class attribute as a comment, so the class starts a line later there and the inline ignore above the attribute matched nothing. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- build/phpstan.neon | 6 ++++++ src/Type/Php/IntdivFunctionReturnTypeExtension.php | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build/phpstan.neon b/build/phpstan.neon index b2686a5e811..8e14cc3d5c0 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -135,6 +135,12 @@ parameters: count: 1 path: ../src/Diagnose/PHPStanDiagnoseExtension.php - identifier: ternary.shortNotAllowed + - + # operators take the configured PHP version, like InitializerExprTypeResolver does; + # not ignored inline because PHP 7.4 reads the class attribute as a comment, + # so the error is on a different line there + identifier: phpstan.phpVersionInjection + path: ../src/Type/Php/IntdivFunctionReturnTypeExtension.php - identifier: shipmonk.deadMethod path: ../src/Internal/CombinationsHelper.php diff --git a/src/Type/Php/IntdivFunctionReturnTypeExtension.php b/src/Type/Php/IntdivFunctionReturnTypeExtension.php index a91eb1719c1..560f60a6a57 100644 --- a/src/Type/Php/IntdivFunctionReturnTypeExtension.php +++ b/src/Type/Php/IntdivFunctionReturnTypeExtension.php @@ -12,8 +12,6 @@ use PHPStan\Type\Type; use function count; -// operators take the configured PHP version, like InitializerExprTypeResolver does -// @phpstan-ignore phpstan.phpVersionInjection #[AutowiredService] final class IntdivFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From 28406647e9e709c55a0afbceb39f716b3d739f15 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 03:15:34 +0000 Subject: [PATCH 14/14] Add regression test for #11732 Closes https://github.com/phpstan/phpstan/issues/11732 Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- .../Analyser/LooseConstComparisonPhp7Test.php | 1 + .../Analyser/LooseConstComparisonPhp8Test.php | 1 + tests/PHPStan/Analyser/data/bug-11732-php7.php | 15 +++++++++++++++ tests/PHPStan/Analyser/data/bug-11732-php8.php | 15 +++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-11732-php7.php create mode 100644 tests/PHPStan/Analyser/data/bug-11732-php8.php diff --git a/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php b/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php index 3139b7a5b98..86b1f1b4bc9 100644 --- a/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php +++ b/tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php @@ -13,6 +13,7 @@ public static function dataFileAsserts(): iterable // compares constants according to the php-version phpstan configuration, // _NOT_ the current php runtime version yield from self::gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php7.php'); + yield from self::gatherAssertTypes(__DIR__ . '/data/bug-11732-php7.php'); } /** diff --git a/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php b/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php index 35ade6c0cd7..a0e5d2065fd 100644 --- a/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php +++ b/tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php @@ -13,6 +13,7 @@ public static function dataFileAsserts(): iterable // compares constants according to the php-version phpstan configuration, // _NOT_ the current php runtime version yield from self::gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php8.php'); + yield from self::gatherAssertTypes(__DIR__ . '/data/bug-11732-php8.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-11732-php7.php b/tests/PHPStan/Analyser/data/bug-11732-php7.php new file mode 100644 index 00000000000..463522c8a3c --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11732-php7.php @@ -0,0 +1,15 @@ +