From 27c1e33696464e632378943389c1b4f10877d370 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 20:04:52 +0000 Subject: [PATCH 01/15] Derive the values loosely equal to a scalar from its truthiness, number and strings The always-equal values of a scalar no longer come from testing a fixed list of candidate values. null and the booleans follow its truthiness, the numbers its number, and the strings are its own, its number's and the falsy ones, printed independently of the precision ini settings. The numbers that can equal a number type are read from its toNumberSet() instead of instanceof checks on the integer and float types. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/LooseComparisonHelper.php | 286 ++++++++++++------- tests/PHPStan/Analyser/nsrt/equal-narrow.php | 14 + 2 files changed, 202 insertions(+), 98 deletions(-) diff --git a/src/Type/LooseComparisonHelper.php b/src/Type/LooseComparisonHelper.php index 2ad5ef58ee0..0bbcf6103dd 100644 --- a/src/Type/LooseComparisonHelper.php +++ b/src/Type/LooseComparisonHelper.php @@ -11,7 +11,6 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use function abs; @@ -20,7 +19,6 @@ use function count; use function floor; use function is_bool; -use function is_finite; use function is_float; use function is_infinite; use function is_int; @@ -28,9 +26,9 @@ use function is_numeric; use function is_string; use function preg_match; +use function sprintf; use function strcmp; use const INF; -use const NAN; use const PHP_INT_MAX; use const PHP_INT_MIN; @@ -52,6 +50,10 @@ final class LooseComparisonHelper // a float with a larger magnitude is not exact enough to tell its neighbouring integers apart private const EXACT_FLOAT_INTEGER_LIMIT = 9007199254740992; + // the significant digits every double can be told apart by, and those that print it without noise + private const EXACT_FLOAT_DIGITS = 17; + private const SHORT_FLOAT_DIGITS = 15; + /** * The values $type can loosely equal: `$type == $x` is false for every $x outside of it. */ @@ -76,33 +78,14 @@ public static function getPossiblyLooselyEqualType(Type $type, PhpVersion $phpVe return TypeCombinator::union(StaticTypeFactory::falsey(), self::createNumberLikeObjectType($phpVersion)); } - if ($type->isInteger()->yes()) { - $integerType = $type instanceof IntegerRangeType || $type instanceof ConstantIntegerType ? $type : new IntegerType(); - [$min, $max] = $type instanceof IntegerRangeType - ? [$type->getMin() ?? PHP_INT_MIN, $type->getMax() ?? PHP_INT_MAX] - : ($type instanceof ConstantIntegerType ? [$type->getValue(), $type->getValue()] : [PHP_INT_MIN, PHP_INT_MAX]); - - return TypeCombinator::union( - self::getNumberPartnerType($integerType, (float) $min, (float) $max, $phpVersion), - self::getBooleanAndNullPartnerType($min <= 0 && $max >= 0, $min !== 0 || $max !== 0), - ); - } - - if ($type->isFloat()->yes()) { - if ($type instanceof FloatRangeType) { - [$min, $max, $nan] = [$type->getMin(), $type->getMax(), false]; - } elseif ($type instanceof ConstantFloatType) { - $value = $type->getValue(); - $nan = is_nan($value); - [$min, $max] = $nan ? [INF, -INF] : [$value, $value]; - } else { - [$min, $max, $nan] = [-INF, INF, true]; + if ($type->isInteger()->yes() || $type->isFloat()->yes()) { + $numbers = $type->toNumberSet(); + if ($numbers !== null) { + return TypeCombinator::union( + self::getNumberPartnerType($numbers, $phpVersion), + self::getBooleanAndNullPartnerType(self::canBeZero($numbers), self::canBeNonZero($numbers)), + ); } - - return TypeCombinator::union( - self::getNumberPartnerType(self::getIntegerTypeInInterval($min, $max), $min, $max, $phpVersion), - self::getBooleanAndNullPartnerType($min <= 0.0 && $max >= 0.0, $nan || $min !== 0.0 || $max !== 0.0), - ); } if ($type->isString()->yes()) { @@ -194,43 +177,7 @@ public static function getAlwaysLooselyEqualType(Type $type, PhpVersion $phpVers $values = $type->getConstantScalarValues(); if (count($values) === 1) { - $value = $values[0]; - $candidates = [null, false, true, 0, 0.0, '', '0', $value]; - if (is_int($value)) { - $candidates[] = (float) $value; - $candidates[] = (string) $value; - } elseif (is_float($value)) { - $candidates[] = 'NAN'; - $candidates[] = 'INF'; - $candidates[] = '-INF'; - if (is_finite($value) && floor($value) === $value && abs($value) <= self::EXACT_FLOAT_INTEGER_LIMIT) { - $candidates[] = (int) $value; - } - } elseif (is_string($value)) { - $candidates[] = NAN; - $candidates[] = INF; - $candidates[] = -INF; - $number = self::parseNumericString($value, $phpVersion); - $number = $number !== null ? $number[0] : self::parseNumericPrefix($value) ?? 0; - $candidates[] = (float) $number; - if (is_int($number)) { - $candidates[] = $number; - } elseif (is_finite($number) && floor($number) === $number && abs($number) <= self::EXACT_FLOAT_INTEGER_LIMIT) { - $candidates[] = (int) $number; - } - } - - // null compares with an array by its emptiness - $types = $value === null ? [new ConstantArrayType([], [])] : []; - foreach ($candidates as $candidate) { - if (self::compareValues($value, $candidate, $phpVersion) !== 0) { - continue; - } - - $types[] = self::createValueType($candidate); - } - - return TypeCombinator::union(...$types); + return self::getTypeLooselyEqualTo($values[0], $phpVersion); } if ($type->isEnum()->yes() && count($type->getEnumCases()) === 1) { @@ -338,38 +285,191 @@ public static function looseCompareInVersions(Type $leftType, Type $rightType, a } /** - * The numbers, numeric strings, objects and resources that can equal a number of the - * float interval, whose integers are $integerType. Booleans and null are left out, - * NAN equals none of them. + * The values that loosely equal $value, of the kinds a type can describe: null and the + * booleans by its truthiness, the numbers equal to its number, and its string forms. */ - private static function getNumberPartnerType(Type $integerType, float $min, float $max, PhpVersion $phpVersion): Type + private static function getTypeLooselyEqualTo(bool|float|int|string|null $value, PhpVersion $phpVersion): Type { - $types = [$integerType]; - if ($min <= $max) { + // null compares with a string as with '', with anything else by truthiness, like the booleans + $types = [new ConstantBooleanType((bool) $value)]; + if (is_string($value) ? $value === '' : !(bool) $value) { + $types[] = new NullType(); + } + if ($value === null) { + // and with an array by its emptiness + $types[] = new ConstantArrayType([], []); + } + + $number = self::getLooselyEqualNumber($value, $phpVersion); + if ($number !== null) { + // both zeros + $types[] = FloatRangeType::fromInterval((float) $number, (float) $number); + if (is_int($number)) { + $types[] = new ConstantIntegerType($number); + } elseif (floor($number) === $number && abs($number) <= self::EXACT_FLOAT_INTEGER_LIMIT) { + $types[] = new ConstantIntegerType((int) $number); + } + } + + // the string itself, the number's own string, and the strings that are falsy + $strings = ['', '0']; + if ($number !== null) { + $strings[] = self::getNumberString($number); + } + if (is_string($value)) { + $strings[] = $value; + } + foreach ($strings as $string) { + if (self::compareValues($value, $string, $phpVersion) !== 0) { + continue; + } + + $types[] = new ConstantStringType($string); + } + + return TypeCombinator::union(...$types); + } + + /** + * The number every number loosely equal to $value equals, or null when no number, + * or more than one, equals it. + */ + private static function getLooselyEqualNumber(bool|float|int|string|null $value, PhpVersion $phpVersion): int|float|null + { + if ($value === null) { + return 0; + } + if (is_bool($value) || (is_float($value) && is_nan($value))) { + return null; + } + if (!is_string($value)) { + return $value; + } + + $number = self::parseNumericString($value, $phpVersion); + if ($number !== null) { + return $number[0]; + } + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + // the number the string starts with + return self::parseNumericPrefix($value) ?? 0; + } + + // a number compares with a non-numeric string as its own string + foreach ([INF, -INF] as $infinity) { + if ((string) $infinity === $value) { + return $infinity; + } + } + + return null; + } + + /** + * The shortest string that reads back as $number, independent of the precision ini settings. + */ + private static function getNumberString(int|float $number): string + { + if (is_int($number)) { + return (string) $number; + } + if (is_infinite($number)) { + return $number > 0 ? 'INF' : '-INF'; + } + + $string = sprintf('%.' . self::SHORT_FLOAT_DIGITS . 'G', $number); + if ((float) $string === $number) { + return $string; + } + + return sprintf('%.' . self::EXACT_FLOAT_DIGITS . 'G', $number); + } + + /** + * The numbers, numeric strings, objects and resources that can equal a number of + * $numbers. Booleans and null are left out, NAN equals none of them. + */ + private static function getNumberPartnerType(NumberSet $numbers, PhpVersion $phpVersion): Type + { + // an integer and a float are equal when the integer converted to float is the float + $integerTypes = []; + $floatIntervals = []; + foreach ($numbers->getIntegerIntervals() as [$min, $max]) { + $integerTypes[] = IntegerRangeType::fromInterval($min, $max); + $floatIntervals[] = [(float) ($min ?? PHP_INT_MIN), (float) ($max ?? PHP_INT_MAX)]; + } + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + $integerTypes[] = self::getIntegerTypeInInterval($min, $max); + $floatIntervals[] = [$min, $max]; + } + if ($floatIntervals === []) { + return new NeverType(); + } + + $integerType = TypeCombinator::union(...$integerTypes); + $types = [$integerType, self::createNumericStringType()]; + $containsOne = false; + $containsInfinity = false; + $containsNegativeInfinity = false; + foreach ($floatIntervals as [$min, $max]) { $types[] = FloatRangeType::fromInterval($min, $max); - $types[] = self::createNumericStringType(); - // an object compares as 1, unless it is number-like - $types[] = $min <= 1.0 && $max >= 1.0 ? new ObjectWithoutClassType() : self::createNumberLikeObjectType($phpVersion); - if ($max >= 1.0 && !$integerType->isInteger()->no()) { - // as its integer ID - $types[] = new ResourceType(); + $containsOne = $containsOne || ($min <= 1.0 && $max >= 1.0); + $containsInfinity = $containsInfinity || $max === INF; + $containsNegativeInfinity = $containsNegativeInfinity || $min === -INF; + } + + // an object compares as 1, unless it is number-like + $types[] = $containsOne ? new ObjectWithoutClassType() : self::createNumberLikeObjectType($phpVersion); + if (!TypeCombinator::intersect($integerType, IntegerRangeType::fromInterval(1, null))->isInteger()->no()) { + // as its integer ID + $types[] = new ResourceType(); + } + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + // as the number the string starts with + $types[] = new StringType(); + } else { + // as its own string + if ($containsInfinity) { + $types[] = new ConstantStringType('INF'); } - if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { - // as the number the string starts with - $types[] = new StringType(); - } else { - if ($max === INF) { - $types[] = new ConstantStringType('INF'); - } - if ($min === -INF) { - $types[] = new ConstantStringType('-INF'); - } + if ($containsNegativeInfinity) { + $types[] = new ConstantStringType('-INF'); } } return TypeCombinator::union(...$types); } + private static function canBeZero(NumberSet $numbers): bool + { + foreach ($numbers->getIntegerIntervals() as [$min, $max]) { + if (($min ?? PHP_INT_MIN) <= 0 && ($max ?? PHP_INT_MAX) >= 0) { + return true; + } + } + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + if ($min <= 0.0 && $max >= 0.0) { + return true; + } + } + + return false; + } + + private static function canBeNonZero(NumberSet $numbers): bool + { + if ($numbers->hasNan()) { + return true; + } + foreach ([...$numbers->getIntegerIntervals(), ...$numbers->getFloatIntervals()] as [$min, $max]) { + if ($min === null || $min !== $max || (float) $min !== 0.0) { + return true; + } + } + + return false; + } + private static function getBooleanAndNullPartnerType(bool $canBeZero, bool $canBeNonZero): Type { $types = []; @@ -474,16 +574,6 @@ private static function createNumericStringType(): Type return new IntersectionType([new StringType(), new AccessoryNumericStringType()]); } - private static function createValueType(bool|float|int|string|null $value): Type - { - if (is_float($value) && !is_nan($value)) { - // both zeros - return FloatRangeType::fromInterval($value, $value); - } - - return ConstantTypeHelper::getTypeFromValue($value); - } - public static function compareConstantScalars(ConstantScalarType $leftType, ConstantScalarType $rightType, PhpVersion $phpVersion): BooleanType { return new ConstantBooleanType(self::compareValues($leftType->getValue(), $rightType->getValue(), $phpVersion) === 0); diff --git a/tests/PHPStan/Analyser/nsrt/equal-narrow.php b/tests/PHPStan/Analyser/nsrt/equal-narrow.php index 679feb7d522..eefd521cb02 100644 --- a/tests/PHPStan/Analyser/nsrt/equal-narrow.php +++ b/tests/PHPStan/Analyser/nsrt/equal-narrow.php @@ -234,3 +234,17 @@ function doEmptyString($x, $y, $z): void assertType("mixed~(''|false|null)", $z); } } + +/** + * @param '1.5'|'1e3'|'1000'|'x' $s + */ +function doNumberStrings(string $s): void +{ + // a number equals its own string, and a numeric string the other strings of its number + if ($s != 1.5) { + assertType("'1000'|'1e3'|'x'", $s); + } + if ($s != '1e3') { + assertType("'1.5'|'x'", $s); + } +} From f1355594acab9e4749bc01562fa5ffd3650eb022 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 20:32:55 +0000 Subject: [PATCH 02/15] Describe the values a type holds with Type::toValueSet() Every type now describes its values as a ValueSet: null, the booleans, a NumberSet of numbers, constant strings and the other strings, arrays by emptiness, objects and resources. A union of types is the union of their sets, an intersection their intersection. getPossiblyLooselyEqualType() and getAlwaysLooselyEqualType() are computed on the ValueSet, so the per-type dispatch in LooseComparisonHelper is gone; it keeps the comparison of two values and the PHP version helpers. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/Accessory/AccessoryArrayListType.php | 6 + .../AccessoryDecimalIntegerStringType.php | 6 + .../Accessory/AccessoryLiteralStringType.php | 6 + .../AccessoryLowercaseStringType.php | 6 + .../Accessory/AccessoryNonEmptyStringType.php | 6 + .../Accessory/AccessoryNonFalsyStringType.php | 6 + .../Accessory/AccessoryNumericStringType.php | 6 + .../AccessoryUppercaseStringType.php | 6 + src/Type/Accessory/NonEmptyArrayType.php | 6 + src/Type/Accessory/OversizedArrayType.php | 6 + src/Type/BooleanType.php | 5 + src/Type/ClosureType.php | 5 + src/Type/Constant/ConstantBooleanType.php | 6 + src/Type/Constant/ConstantStringType.php | 6 + src/Type/FloatType.php | 5 + src/Type/IntegerRangeType.php | 5 + src/Type/IntegerType.php | 5 + src/Type/IntersectionType.php | 10 + src/Type/LooseComparisonHelper.php | 450 +----------- src/Type/NeverType.php | 5 + src/Type/NullType.php | 5 + src/Type/NumberSet.php | 40 ++ src/Type/ObjectShapeType.php | 5 + src/Type/ObjectType.php | 5 + src/Type/ObjectWithoutClassType.php | 5 + src/Type/ResourceType.php | 5 + src/Type/StaticType.php | 5 + src/Type/StringType.php | 5 + src/Type/Traits/ArrayTypeTrait.php | 6 + src/Type/Traits/LateResolvableTypeTrait.php | 6 + src/Type/Traits/OperatorTypeTrait.php | 11 +- src/Type/Type.php | 6 + src/Type/UnionType.php | 10 + src/Type/ValueSet.php | 672 ++++++++++++++++++ turbo-ext/src/AccessoryArrayListType.cpp | 2 + .../src/AccessoryDecimalIntegerStringType.cpp | 2 + turbo-ext/src/AccessoryLiteralStringType.cpp | 2 + .../src/AccessoryLowercaseStringType.cpp | 2 + turbo-ext/src/AccessoryNonEmptyStringType.cpp | 2 + turbo-ext/src/AccessoryNonFalsyStringType.cpp | 2 + turbo-ext/src/AccessoryNumericStringType.cpp | 2 + .../src/AccessoryUppercaseStringType.cpp | 2 + turbo-ext/src/BooleanType.cpp | 6 + turbo-ext/src/ClosureType.cpp | 2 + turbo-ext/src/ConstantBooleanType.cpp | 9 + turbo-ext/src/ConstantStringType.cpp | 7 + turbo-ext/src/FloatType.cpp | 2 + turbo-ext/src/IntegerRangeType.cpp | 2 + turbo-ext/src/IntegerType.cpp | 2 + turbo-ext/src/IntersectionType.cpp | 2 + turbo-ext/src/NeverType.cpp | 2 + turbo-ext/src/NonEmptyArrayType.cpp | 2 + turbo-ext/src/NullType.cpp | 2 + turbo-ext/src/NumberSet.cpp | 48 +- turbo-ext/src/ObjectShapeType.cpp | 2 + turbo-ext/src/ObjectType.cpp | 2 + turbo-ext/src/ObjectWithoutClassType.cpp | 2 + turbo-ext/src/OversizedArrayType.cpp | 2 + turbo-ext/src/ResourceType.cpp | 2 + turbo-ext/src/StaticType.cpp | 2 + turbo-ext/src/StringType.cpp | 2 + turbo-ext/src/TypeTraits.cpp | 62 +- turbo-ext/src/TypeTraits.h | 34 + turbo-ext/src/UnionType.cpp | 2 + .../src/generated/AccessoryArrayListType.h | 6 +- .../AccessoryDecimalIntegerStringType.h | 6 +- .../generated/AccessoryLiteralStringType.h | 6 +- .../generated/AccessoryLowercaseStringType.h | 6 +- .../generated/AccessoryNonEmptyStringType.h | 6 +- .../generated/AccessoryNonFalsyStringType.h | 6 +- .../generated/AccessoryNumericStringType.h | 6 +- .../generated/AccessoryUppercaseStringType.h | 6 +- turbo-ext/src/generated/ArrayTypeTrait.h | 6 +- turbo-ext/src/generated/BooleanType.h | 6 +- turbo-ext/src/generated/ClosureType.h | 6 +- turbo-ext/src/generated/ConstantBooleanType.h | 6 +- turbo-ext/src/generated/ConstantStringType.h | 6 +- turbo-ext/src/generated/FloatType.h | 6 +- turbo-ext/src/generated/IntegerRangeType.h | 6 +- turbo-ext/src/generated/IntegerType.h | 6 +- turbo-ext/src/generated/IntersectionType.h | 6 +- .../src/generated/LateResolvableTypeTrait.h | 6 +- turbo-ext/src/generated/NeverType.h | 6 +- turbo-ext/src/generated/NonEmptyArrayType.h | 6 +- turbo-ext/src/generated/NullType.h | 6 +- turbo-ext/src/generated/NumberSet.h | 219 +++--- turbo-ext/src/generated/ObjectShapeType.h | 6 +- turbo-ext/src/generated/ObjectType.h | 6 +- .../src/generated/ObjectWithoutClassType.h | 6 +- turbo-ext/src/generated/OperatorTypeTrait.h | 132 ++-- turbo-ext/src/generated/OversizedArrayType.h | 6 +- turbo-ext/src/generated/ResourceType.h | 6 +- turbo-ext/src/generated/StaticType.h | 6 +- turbo-ext/src/generated/StringType.h | 6 +- turbo-ext/src/generated/UnionType.h | 6 +- turbo-ext/src/support.cpp | 1 + turbo-ext/src/support.h | 2 + 97 files changed, 1433 insertions(+), 657 deletions(-) create mode 100644 src/Type/ValueSet.php diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index cca0437a806..e2de4196c38 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; /** @api */ @@ -593,4 +594,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createArrays(true, true); + } + } diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index 1483d1246d9..acaa7a757f3 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; /** @@ -494,4 +495,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index d970d999f96..b5eee771f4e 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isLiteralString()')] @@ -415,4 +416,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 70ac7182c39..b7dfd02a5a5 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isLowercaseString()')] @@ -420,4 +421,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 6ffdffcd707..0e1b34886c9 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -37,6 +37,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isNonEmptyString()')] @@ -424,4 +425,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index ebafce2bab7..2861563e36f 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -39,6 +39,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isNonFalsyString()')] @@ -421,4 +422,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index e114df91396..2ae309ce906 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -37,6 +37,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isNumericString()')] @@ -433,4 +434,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index d126956f2f4..909183e16f6 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -36,6 +36,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isUppercaseString()')] @@ -420,4 +421,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index cdf6d9e7b0f..b1d0e5297b2 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -32,6 +32,7 @@ use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isIterableAtLeastOnce()')] @@ -572,4 +573,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createArrays(false, true); + } + } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 115798dfc62..b65f8dab9ef 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -30,6 +30,7 @@ use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; #[InstanceofDeprecated(insteadUse: 'Type::isOversizedArray()')] @@ -542,4 +543,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createArrays(false, true); + } + } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 7a07b9d6422..828159ac8c3 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -214,4 +214,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createBooleans(true, true); + } + } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index f3b22919c75..6469e84b4e7 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -1070,4 +1070,9 @@ public function hasTemplateOrLateResolvableType(): bool return $this->getReturnType()->hasTemplateOrLateResolvableType(); } + public function toValueSet(): ValueSet + { + return ValueSet::createObjects($this); + } + } diff --git a/src/Type/Constant/ConstantBooleanType.php b/src/Type/Constant/ConstantBooleanType.php index 844d0a070d9..30de7733b80 100644 --- a/src/Type/Constant/ConstantBooleanType.php +++ b/src/Type/Constant/ConstantBooleanType.php @@ -20,6 +20,7 @@ use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; /** @api */ @@ -167,4 +168,9 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return $this->scalarLooseCompare($type, $phpVersion); } + public function toValueSet(): ValueSet + { + return ValueSet::createBooleans(!$this->getValue(), $this->getValue()); + } + } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index c321a84d04c..8ca1026ad50 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -48,6 +48,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; use function addcslashes; use function array_unique; @@ -656,4 +657,9 @@ public function toPhpDocNode(): TypeNode return new ConstTypeNode(new ConstExprStringNode($this->value, ConstExprStringNode::SINGLE_QUOTED)); } + public function toValueSet(): ValueSet + { + return ValueSet::createConstantString($this->getValue()); + } + } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index d9f0172303b..40cbabf3f63 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -361,4 +361,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createNumbers($this->toNumberSet()); + } + } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 6b043b9fda3..2c2dda52198 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -817,4 +817,9 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return parent::looseCompare($type, $phpVersion); } + public function toValueSet(): ValueSet + { + return ValueSet::createNumbers($this->toNumberSet()); + } + } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 4320aa19eee..b1a6cb7ed2b 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -221,4 +221,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createNumbers($this->toNumberSet()); + } + } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index c6dcdb585e3..72340073543 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -2142,4 +2142,14 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + $valueSet = ValueSet::createUnknown(); + foreach ($this->types as $type) { + $valueSet = $valueSet->intersect($type->toValueSet()); + } + + return $valueSet; + } + } diff --git a/src/Type/LooseComparisonHelper.php b/src/Type/LooseComparisonHelper.php index 0bbcf6103dd..e5a70c12207 100644 --- a/src/Type/LooseComparisonHelper.php +++ b/src/Type/LooseComparisonHelper.php @@ -7,17 +7,9 @@ use PHPStan\Php\PhpVersions; use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ReferencedByTurboExtension; -use PHPStan\Type\Accessory\AccessoryNumericStringType; -use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; -use function abs; use function array_slice; -use function ceil; use function count; -use function floor; use function is_bool; use function is_float; use function is_infinite; @@ -26,11 +18,7 @@ use function is_numeric; use function is_string; use function preg_match; -use function sprintf; use function strcmp; -use const INF; -use const PHP_INT_MAX; -use const PHP_INT_MIN; /** * Evaluates PHP's comparison of two scalar values (`==`, `<`, `<=`, `<=>`) the way @@ -47,150 +35,6 @@ final class LooseComparisonHelper // the integer digits are captured only for a number without a fraction, the exponent separately private const NUMBER = '([+-]?(?:\d+\.\d*|\.\d+|(\d+))([eE][+-]?\d+)?)'; - // a float with a larger magnitude is not exact enough to tell its neighbouring integers apart - private const EXACT_FLOAT_INTEGER_LIMIT = 9007199254740992; - - // the significant digits every double can be told apart by, and those that print it without noise - private const EXACT_FLOAT_DIGITS = 17; - private const SHORT_FLOAT_DIGITS = 15; - - /** - * The values $type can loosely equal: `$type == $x` is false for every $x outside of it. - */ - public static function getPossiblyLooselyEqualType(Type $type, PhpVersion $phpVersion): Type - { - if ($type instanceof UnionType) { - $types = []; - foreach ($type->getTypes() as $innerType) { - $types[] = self::getPossiblyLooselyEqualType($innerType, $phpVersion); - } - - return TypeCombinator::union(...$types); - } - - if ($type->isNull()->yes()) { - return self::getAlwaysLooselyEqualType($type, $phpVersion); - } - if ($type->isTrue()->yes()) { - return StaticTypeFactory::truthy(); - } - if ($type->isFalse()->yes()) { - return TypeCombinator::union(StaticTypeFactory::falsey(), self::createNumberLikeObjectType($phpVersion)); - } - - if ($type->isInteger()->yes() || $type->isFloat()->yes()) { - $numbers = $type->toNumberSet(); - if ($numbers !== null) { - return TypeCombinator::union( - self::getNumberPartnerType($numbers, $phpVersion), - self::getBooleanAndNullPartnerType(self::canBeZero($numbers), self::canBeNonZero($numbers)), - ); - } - } - - if ($type->isString()->yes()) { - $constantStrings = $type->getConstantStrings(); - if (count($constantStrings) === 1) { - return self::getConstantStringPartnerType($constantStrings[0]->getValue(), $phpVersion); - } - - // a string compares with null as with '', and with a bool by its truthiness - $booleanAndNull = [new ConstantBooleanType(true)]; - if (!$type->isNonEmptyString()->yes()) { - $booleanAndNull[] = new NullType(); - } - if (!$type->isNonFalsyString()->yes()) { - $booleanAndNull[] = new ConstantBooleanType(false); - } - $booleanAndNull = TypeCombinator::union(...$booleanAndNull); - - if ($type->isNumericString()->yes()) { - // a numeric string compares with a non-numeric one byte by byte, so they differ - return TypeCombinator::union( - new IntegerType(), - FloatRangeType::fromInterval(-INF, INF), - self::createNumericStringType(), - self::createStringableObjectType($phpVersion), - new ResourceType(), - $booleanAndNull, - ); - } - - // a non-numeric string equals only itself - return TypeCombinator::union( - $type, - $type->isNumericString()->no() ? new NeverType() : self::createNumericStringType(), - new IntegerType(), - new FloatType(), - self::createStringableObjectType($phpVersion), - new ResourceType(), - $booleanAndNull, - ); - } - - if ($type->isEnum()->yes() && count($type->getEnumCases()) === 1) { - return self::getAlwaysLooselyEqualType($type, $phpVersion); - } - - if ($type->isArray()->yes()) { - $isNonEmpty = $type->isIterableAtLeastOnce(); - if ($isNonEmpty->no()) { - return self::getAlwaysLooselyEqualType($type, $phpVersion); - } - - $arrayType = new ArrayType(new MixedType(), new MixedType()); - if ($isNonEmpty->yes()) { - return TypeCombinator::union(new ConstantBooleanType(true), TypeCombinator::intersect($arrayType, new NonEmptyArrayType())); - } - - return TypeCombinator::union($arrayType, new BooleanType(), new NullType()); - } - - return new MixedType(); - } - - /** - * The values $type always loosely equals: `$type == $x` is true for every $x of it. - */ - public static function getAlwaysLooselyEqualType(Type $type, PhpVersion $phpVersion): Type - { - if ($type instanceof UnionType) { - // one member at a time: intersecting all the unions at once multiplies them out - $alwaysEqualType = null; - foreach ($type->getTypes() as $innerType) { - $innerAlwaysEqualType = self::getAlwaysLooselyEqualType($innerType, $phpVersion); - $alwaysEqualType = $alwaysEqualType === null ? $innerAlwaysEqualType : TypeCombinator::intersect($alwaysEqualType, $innerAlwaysEqualType); - if ($alwaysEqualType instanceof NeverType) { - break; - } - } - - return $alwaysEqualType ?? new NeverType(); - } - - if ($type->isTrue()->yes()) { - return new MixedType(subtractedType: TypeCombinator::union(StaticTypeFactory::falsey(), self::createNumberLikeObjectType($phpVersion))); - } - if ($type->isFalse()->yes()) { - return StaticTypeFactory::falsey(); - } - - $values = $type->getConstantScalarValues(); - if (count($values) === 1) { - return self::getTypeLooselyEqualTo($values[0], $phpVersion); - } - - if ($type->isEnum()->yes() && count($type->getEnumCases()) === 1) { - return TypeCombinator::union($type->getEnumCases()[0], new ConstantBooleanType(true)); - } - - if ($type->isArray()->yes() && $type->isIterableAtLeastOnce()->no()) { - return TypeCombinator::union(new NullType(), new ConstantBooleanType(false), new ConstantArrayType([], [])); - } - - return new NeverType(); - } - /** * Whether a mixed subject that loosely equals a value of $possiblyLooselyEqualType * should stay mixed: narrowing it would add objects of classes like Stringable or GMP @@ -284,296 +128,6 @@ public static function looseCompareInVersions(Type $leftType, Type $rightType, a return $result; } - /** - * The values that loosely equal $value, of the kinds a type can describe: null and the - * booleans by its truthiness, the numbers equal to its number, and its string forms. - */ - private static function getTypeLooselyEqualTo(bool|float|int|string|null $value, PhpVersion $phpVersion): Type - { - // null compares with a string as with '', with anything else by truthiness, like the booleans - $types = [new ConstantBooleanType((bool) $value)]; - if (is_string($value) ? $value === '' : !(bool) $value) { - $types[] = new NullType(); - } - if ($value === null) { - // and with an array by its emptiness - $types[] = new ConstantArrayType([], []); - } - - $number = self::getLooselyEqualNumber($value, $phpVersion); - if ($number !== null) { - // both zeros - $types[] = FloatRangeType::fromInterval((float) $number, (float) $number); - if (is_int($number)) { - $types[] = new ConstantIntegerType($number); - } elseif (floor($number) === $number && abs($number) <= self::EXACT_FLOAT_INTEGER_LIMIT) { - $types[] = new ConstantIntegerType((int) $number); - } - } - - // the string itself, the number's own string, and the strings that are falsy - $strings = ['', '0']; - if ($number !== null) { - $strings[] = self::getNumberString($number); - } - if (is_string($value)) { - $strings[] = $value; - } - foreach ($strings as $string) { - if (self::compareValues($value, $string, $phpVersion) !== 0) { - continue; - } - - $types[] = new ConstantStringType($string); - } - - return TypeCombinator::union(...$types); - } - - /** - * The number every number loosely equal to $value equals, or null when no number, - * or more than one, equals it. - */ - private static function getLooselyEqualNumber(bool|float|int|string|null $value, PhpVersion $phpVersion): int|float|null - { - if ($value === null) { - return 0; - } - if (is_bool($value) || (is_float($value) && is_nan($value))) { - return null; - } - if (!is_string($value)) { - return $value; - } - - $number = self::parseNumericString($value, $phpVersion); - if ($number !== null) { - return $number[0]; - } - if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { - // the number the string starts with - return self::parseNumericPrefix($value) ?? 0; - } - - // a number compares with a non-numeric string as its own string - foreach ([INF, -INF] as $infinity) { - if ((string) $infinity === $value) { - return $infinity; - } - } - - return null; - } - - /** - * The shortest string that reads back as $number, independent of the precision ini settings. - */ - private static function getNumberString(int|float $number): string - { - if (is_int($number)) { - return (string) $number; - } - if (is_infinite($number)) { - return $number > 0 ? 'INF' : '-INF'; - } - - $string = sprintf('%.' . self::SHORT_FLOAT_DIGITS . 'G', $number); - if ((float) $string === $number) { - return $string; - } - - return sprintf('%.' . self::EXACT_FLOAT_DIGITS . 'G', $number); - } - - /** - * The numbers, numeric strings, objects and resources that can equal a number of - * $numbers. Booleans and null are left out, NAN equals none of them. - */ - private static function getNumberPartnerType(NumberSet $numbers, PhpVersion $phpVersion): Type - { - // an integer and a float are equal when the integer converted to float is the float - $integerTypes = []; - $floatIntervals = []; - foreach ($numbers->getIntegerIntervals() as [$min, $max]) { - $integerTypes[] = IntegerRangeType::fromInterval($min, $max); - $floatIntervals[] = [(float) ($min ?? PHP_INT_MIN), (float) ($max ?? PHP_INT_MAX)]; - } - foreach ($numbers->getFloatIntervals() as [$min, $max]) { - $integerTypes[] = self::getIntegerTypeInInterval($min, $max); - $floatIntervals[] = [$min, $max]; - } - if ($floatIntervals === []) { - return new NeverType(); - } - - $integerType = TypeCombinator::union(...$integerTypes); - $types = [$integerType, self::createNumericStringType()]; - $containsOne = false; - $containsInfinity = false; - $containsNegativeInfinity = false; - foreach ($floatIntervals as [$min, $max]) { - $types[] = FloatRangeType::fromInterval($min, $max); - $containsOne = $containsOne || ($min <= 1.0 && $max >= 1.0); - $containsInfinity = $containsInfinity || $max === INF; - $containsNegativeInfinity = $containsNegativeInfinity || $min === -INF; - } - - // an object compares as 1, unless it is number-like - $types[] = $containsOne ? new ObjectWithoutClassType() : self::createNumberLikeObjectType($phpVersion); - if (!TypeCombinator::intersect($integerType, IntegerRangeType::fromInterval(1, null))->isInteger()->no()) { - // as its integer ID - $types[] = new ResourceType(); - } - if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { - // as the number the string starts with - $types[] = new StringType(); - } else { - // as its own string - if ($containsInfinity) { - $types[] = new ConstantStringType('INF'); - } - if ($containsNegativeInfinity) { - $types[] = new ConstantStringType('-INF'); - } - } - - return TypeCombinator::union(...$types); - } - - private static function canBeZero(NumberSet $numbers): bool - { - foreach ($numbers->getIntegerIntervals() as [$min, $max]) { - if (($min ?? PHP_INT_MIN) <= 0 && ($max ?? PHP_INT_MAX) >= 0) { - return true; - } - } - foreach ($numbers->getFloatIntervals() as [$min, $max]) { - if ($min <= 0.0 && $max >= 0.0) { - return true; - } - } - - return false; - } - - private static function canBeNonZero(NumberSet $numbers): bool - { - if ($numbers->hasNan()) { - return true; - } - foreach ([...$numbers->getIntegerIntervals(), ...$numbers->getFloatIntervals()] as [$min, $max]) { - if ($min === null || $min !== $max || (float) $min !== 0.0) { - return true; - } - } - - return false; - } - - private static function getBooleanAndNullPartnerType(bool $canBeZero, bool $canBeNonZero): Type - { - $types = []; - if ($canBeZero) { - $types[] = new NullType(); - $types[] = new ConstantBooleanType(false); - } - if ($canBeNonZero) { - $types[] = new ConstantBooleanType(true); - } - - return TypeCombinator::union(...$types); - } - - private static function getConstantStringPartnerType(string $value, PhpVersion $phpVersion): Type - { - $types = [ - new ConstantStringType($value), - self::createStringableObjectType($phpVersion), - self::getBooleanAndNullPartnerType(false, $value !== '' && $value !== '0'), - self::getAlwaysLooselyEqualType(new ConstantStringType($value), $phpVersion), - ]; - - $number = self::parseNumericString($value, $phpVersion); - if ($number !== null) { - $number = $number[0]; - $types[] = self::createNumericStringType(); - $types[] = self::createNumberType($number); - if ($number >= 1 && floor($number) === (float) $number) { - $types[] = new ResourceType(); - } - } elseif (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { - $number = self::parseNumericPrefix($value) ?? 0; - $types[] = self::createNumberType($number); - if ($number >= 1 && floor($number) === (float) $number) { - $types[] = new ResourceType(); - } - } - - return TypeCombinator::union(...$types); - } - - /** The integers and floats equal to $number. */ - private static function createNumberType(int|float $number): Type - { - $float = (float) $number; - $integerType = is_int($number) ? new ConstantIntegerType($number) : self::getIntegerTypeInInterval($float, $float); - - return TypeCombinator::union($integerType, FloatRangeType::fromInterval($float, $float)); - } - - /** The integers that compare equal to a float of the interval. */ - private static function getIntegerTypeInInterval(float $min, float $max): Type - { - if ($min > $max || $min > (float) PHP_INT_MAX || $max < (float) PHP_INT_MIN) { - return new NeverType(); - } - - if ($min < -self::EXACT_FLOAT_INTEGER_LIMIT) { - $integerMin = null; - } elseif ($min > self::EXACT_FLOAT_INTEGER_LIMIT) { - $integerMin = self::EXACT_FLOAT_INTEGER_LIMIT; - } else { - $integerMin = (int) ceil($min); - } - - if ($max > self::EXACT_FLOAT_INTEGER_LIMIT) { - $integerMax = null; - } elseif ($max < -self::EXACT_FLOAT_INTEGER_LIMIT) { - $integerMax = -self::EXACT_FLOAT_INTEGER_LIMIT; - } else { - $integerMax = (int) floor($max); - } - - return IntegerRangeType::fromInterval($integerMin, $integerMax); - } - - /** GMP, BcMath\Number (since PHP 8.4) and SimpleXMLElement objects compare as their number, and can be falsy. */ - private static function createNumberLikeObjectType(PhpVersion $phpVersion): Type - { - $types = [new ObjectType('GMP'), new ObjectType('SimpleXMLElement')]; - if ($phpVersion->getVersionId() >= 80400) { - $types[] = new ObjectType('BcMath\\Number'); - } - - return TypeCombinator::union(...$types); - } - - /** The objects that compare with a string as their string. */ - private static function createStringableObjectType(PhpVersion $phpVersion): Type - { - // a class with __toString() implements Stringable since PHP 8.0 - if ($phpVersion->getVersionId() < 80000) { - return new ObjectWithoutClassType(); - } - - return TypeCombinator::union(new ObjectType('Stringable'), new ObjectType('GMP')); - } - - private static function createNumericStringType(): Type - { - return new IntersectionType([new StringType(), new AccessoryNumericStringType()]); - } - public static function compareConstantScalars(ConstantScalarType $leftType, ConstantScalarType $rightType, PhpVersion $phpVersion): BooleanType { return new ConstantBooleanType(self::compareValues($leftType->getValue(), $rightType->getValue(), $phpVersion) === 0); @@ -687,7 +241,7 @@ private static function compareBytes(string $left, string $right): int * * @return array{int|float, int}|null */ - private static function parseNumericString(string $value, PhpVersion $phpVersion): ?array + public static function parseNumericString(string $value, PhpVersion $phpVersion): ?array { $pattern = $phpVersion->allowsTrailingWhitespaceInNumericStrings() ? '/^[' . self::WHITESPACE . ']*' . self::NUMBER . '[' . self::WHITESPACE . ']*$/D' @@ -702,7 +256,7 @@ private static function parseNumericString(string $value, PhpVersion $phpVersion /** * The number a string starts with, as PHP 7 compares a string with a number. */ - private static function parseNumericPrefix(string $value): int|float|null + public static function parseNumericPrefix(string $value): int|float|null { if (preg_match('/^[' . self::WHITESPACE . ']*' . self::NUMBER . '/', $value, $matches) !== 1) { return null; diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 5d484908cb3..8ce79def876 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -675,4 +675,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createEmpty(); + } + } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 6c88b5c3b01..104b3ae3b95 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -438,4 +438,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createNull(); + } + } diff --git a/src/Type/NumberSet.php b/src/Type/NumberSet.php index 5b32aa4a798..b3cbbd26892 100644 --- a/src/Type/NumberSet.php +++ b/src/Type/NumberSet.php @@ -77,6 +77,46 @@ public function hasNan(): bool return $this->nan; } + public function union(self $other): self + { + return new self( + [...$this->integerIntervals, ...$other->integerIntervals], + [...$this->floatIntervals, ...$other->floatIntervals], + $this->nan || $other->nan, + ); + } + + public function intersect(self $other): self + { + $integerIntervals = []; + foreach ($this->integerIntervals as [$leftMin, $leftMax]) { + foreach ($other->integerIntervals as [$rightMin, $rightMax]) { + $min = $leftMin === null || $rightMin === null ? $leftMin ?? $rightMin : max($leftMin, $rightMin); + $max = $leftMax === null || $rightMax === null ? $leftMax ?? $rightMax : min($leftMax, $rightMax); + if ($min !== null && $max !== null && $min > $max) { + continue; + } + + $integerIntervals[] = [$min, $max]; + } + } + + $floatIntervals = []; + foreach ($this->floatIntervals as [$leftMin, $leftMax]) { + foreach ($other->floatIntervals as [$rightMin, $rightMax]) { + $min = max($leftMin, $rightMin); + $max = min($leftMax, $rightMax); + if ($min > $max) { + continue; + } + + $floatIntervals[] = [$min, $max]; + } + } + + return new self($integerIntervals, $floatIntervals, $this->nan && $other->nan); + } + /** Models the + operator. */ public function add(self $right, PhpVersion $phpVersion): Type { diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index d1c18cf7b4b..68d0bc88684 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -609,4 +609,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createObjects($this); + } + } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index f3bff0aab23..90cbb62f36b 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -2155,4 +2155,9 @@ public function hasTemplateOrLateResolvableType(): bool return $this->subtractedType->hasTemplateOrLateResolvableType(); } + public function toValueSet(): ValueSet + { + return ValueSet::createObjects($this); + } + } diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 74f3f129c26..b58e28d4def 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -239,4 +239,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createObjects($this); + } + } diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index 00afffd372e..3749c2cb8b4 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -144,4 +144,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createResources(); + } + } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index d8018a55b70..d144c4f491f 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -950,4 +950,9 @@ public function hasTemplateOrLateResolvableType(): bool return $this->getStaticObjectType()->hasTemplateOrLateResolvableType(); } + public function toValueSet(): ValueSet + { + return ValueSet::createObjects($this); + } + } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index d885c1dd125..03a76d20b38 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -365,4 +365,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createStrings($this); + } + } diff --git a/src/Type/Traits/ArrayTypeTrait.php b/src/Type/Traits/ArrayTypeTrait.php index 06a259eb62b..23465816710 100644 --- a/src/Type/Traits/ArrayTypeTrait.php +++ b/src/Type/Traits/ArrayTypeTrait.php @@ -12,6 +12,7 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\ValueSet; trait ArrayTypeTrait { @@ -222,4 +223,9 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type : $arrayType; } + public function toValueSet(): ValueSet + { + return ValueSet::createArrays(!$this->isIterableAtLeastOnce()->yes(), !$this->isIterableAtLeastOnce()->no()); + } + } diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 89fcdbd90cf..201c561e6fd 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -22,6 +22,7 @@ use PHPStan\Type\NativeTypePosition; use PHPStan\Type\NeverType; use PHPStan\Type\Type; +use PHPStan\Type\ValueSet; /** @api */ trait LateResolvableTypeTrait @@ -721,4 +722,9 @@ public function hasTemplateOrLateResolvableType(): bool return true; } + public function toValueSet(): ValueSet + { + return $this->resolve()->toValueSet(); + } + } diff --git a/src/Type/Traits/OperatorTypeTrait.php b/src/Type/Traits/OperatorTypeTrait.php index b05c20a5f33..996b3fed129 100644 --- a/src/Type/Traits/OperatorTypeTrait.php +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -4,10 +4,10 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; -use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\NumberSet; use PHPStan\Type\OperatorTypeHelper; use PHPStan\Type\Type; +use PHPStan\Type\ValueSet; trait OperatorTypeTrait { @@ -22,6 +22,11 @@ public function toNumberSet(): ?NumberSet return null; } + public function toValueSet(): ValueSet + { + return ValueSet::createUnknown(); + } + public function arithmeticAdd(Type $right, PhpVersion $phpVersion): Type { return OperatorTypeHelper::add($this, $right, $phpVersion); @@ -84,12 +89,12 @@ public function bitwiseShiftRight(Type $right, PhpVersion $phpVersion): Type public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type { - return LooseComparisonHelper::getPossiblyLooselyEqualType($this, $phpVersion); + return $this->toValueSet()->getPossiblyLooselyEqualType($phpVersion); } public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type { - return LooseComparisonHelper::getAlwaysLooselyEqualType($this, $phpVersion); + return $this->toValueSet()->getAlwaysLooselyEqualType($phpVersion); } public function stringConcatenate(Type $right, PhpVersion $phpVersion): Type diff --git a/src/Type/Type.php b/src/Type/Type.php index 2da53cc9cfc..2f418fac976 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -461,6 +461,12 @@ public function toNumber(): Type; */ public function toNumberSet(): ?NumberSet; + /** + * The values this type holds, by the kinds of values PHP's operators tell apart. + * A union of types is the union of their sets, an intersection their intersection. + */ + public function toValueSet(): ValueSet; + /** Models the bitwise-not (`~$x`) operator. Returns `ErrorType` for types where `~` is undefined. */ public function toBitwiseNotType(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 36828247a7b..0d0991b800f 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -1811,4 +1811,14 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + $valueSet = ValueSet::createEmpty(); + foreach ($this->types as $type) { + $valueSet = $valueSet->union($type->toValueSet()); + } + + return $valueSet; + } + } diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php new file mode 100644 index 00000000000..d3f71505890 --- /dev/null +++ b/src/Type/ValueSet.php @@ -0,0 +1,672 @@ + $constantStrings + */ + private function __construct( + private bool $unknown, + private bool $null = false, + private bool $false = false, + private bool $true = false, + private ?NumberSet $numbers = null, + private array $constantStrings = [], + private ?Type $strings = null, + private bool $emptyArray = false, + private bool $nonEmptyArray = false, + private ?Type $objects = null, + private bool $resources = false, + ) + { + } + + public static function createUnknown(): self + { + return new self(true); + } + + public static function createEmpty(): self + { + return new self(false); + } + + public static function createNull(): self + { + return new self(false, null: true); + } + + public static function createBooleans(bool $false, bool $true): self + { + return new self(false, false: $false, true: $true); + } + + public static function createNumbers(NumberSet $numbers): self + { + return new self(false, numbers: $numbers); + } + + public static function createConstantString(string $value): self + { + return new self(false, constantStrings: [$value]); + } + + /** Strings of a string type: StringType, or an accessory such as AccessoryNumericStringType. */ + public static function createStrings(Type $strings): self + { + return new self(false, strings: $strings); + } + + public static function createArrays(bool $empty, bool $nonEmpty): self + { + return new self(false, emptyArray: $empty, nonEmptyArray: $nonEmpty); + } + + public static function createObjects(Type $objects): self + { + return new self(false, objects: $objects); + } + + public static function createResources(): self + { + return new self(false, resources: true); + } + + public function union(self $other): self + { + if ($this->unknown || $other->unknown) { + return self::createUnknown(); + } + + $constantStrings = $this->constantStrings; + foreach ($other->constantStrings as $constantString) { + if (in_array($constantString, $constantStrings, true)) { + continue; + } + + $constantStrings[] = $constantString; + } + + return new self( + false, + $this->null || $other->null, + $this->false || $other->false, + $this->true || $other->true, + $this->numbers === null || $other->numbers === null ? $this->numbers ?? $other->numbers : $this->numbers->union($other->numbers), + $constantStrings, + self::unionTypes($this->strings, $other->strings), + $this->emptyArray || $other->emptyArray, + $this->nonEmptyArray || $other->nonEmptyArray, + self::unionTypes($this->objects, $other->objects), + $this->resources || $other->resources, + ); + } + + public function intersect(self $other): self + { + if ($this->unknown) { + return $other; + } + if ($other->unknown) { + return $this; + } + + $constantStrings = []; + foreach ([[$this, $other], [$other, $this]] as [$set, $otherSet]) { + foreach ($set->constantStrings as $constantString) { + if ( + in_array($constantString, $constantStrings, true) + || ( + !in_array($constantString, $otherSet->constantStrings, true) + && ($otherSet->strings === null || $otherSet->strings->isSuperTypeOf(new ConstantStringType($constantString))->no()) + ) + ) { + continue; + } + + $constantStrings[] = $constantString; + } + } + + return new self( + false, + $this->null && $other->null, + $this->false && $other->false, + $this->true && $other->true, + $this->numbers === null || $other->numbers === null ? null : $this->numbers->intersect($other->numbers), + $constantStrings, + $this->strings === null || $other->strings === null ? null : TypeCombinator::intersect($this->strings, $other->strings), + $this->emptyArray && $other->emptyArray, + $this->nonEmptyArray && $other->nonEmptyArray, + $this->objects === null || $other->objects === null ? null : TypeCombinator::intersect($this->objects, $other->objects), + $this->resources && $other->resources, + ); + } + + /** + * The values one of this set's values can loosely equal: `$x == $y` is false + * for every $x of the set and every $y outside of the returned type. + */ + public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type + { + if ($this->unknown || $this->resources) { + return new MixedType(); + } + + $types = []; + if ($this->null) { + $types[] = self::getTypeLooselyEqualTo(null, $phpVersion); + } + if ($this->true) { + $types[] = StaticTypeFactory::truthy(); + } + if ($this->false) { + $types[] = StaticTypeFactory::falsey(); + $types[] = self::createNumberLikeObjectType($phpVersion); + } + if ($this->numbers !== null) { + $types[] = self::getNumberPartnerType($this->numbers, $phpVersion); + $types[] = self::getBooleanAndNullPartnerType(self::canBeZero($this->numbers), self::canBeNonZero($this->numbers)); + } + foreach ($this->constantStrings as $constantString) { + $types[] = self::getConstantStringPartnerType($constantString, $phpVersion); + } + if ($this->strings !== null) { + $types[] = self::getStringPartnerType($this->strings, $phpVersion); + } + if ($this->emptyArray || $this->nonEmptyArray) { + $types[] = self::getArrayPartnerType($this->emptyArray, $this->nonEmptyArray); + } + if ($this->objects !== null) { + if (!$this->objects->isEnum()->yes()) { + return new MixedType(); + } + + // an enum case equals only itself, and true + $types[] = $this->objects; + $types[] = new ConstantBooleanType(true); + } + + return TypeCombinator::union(...$types); + } + + /** + * The values every one of this set's values loosely equals: `$x == $y` is true + * for every $x of the set and every $y of the returned type. + */ + public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type + { + if ($this->unknown || $this->strings !== null || $this->nonEmptyArray || $this->resources) { + return new NeverType(); + } + + $types = []; + if ($this->null) { + $types[] = self::getTypeLooselyEqualTo(null, $phpVersion); + } + if ($this->true) { + $types[] = new MixedType(subtractedType: TypeCombinator::union(StaticTypeFactory::falsey(), self::createNumberLikeObjectType($phpVersion))); + } + if ($this->false) { + $types[] = StaticTypeFactory::falsey(); + } + if ($this->numbers !== null) { + foreach ($this->getNumberValues($this->numbers) ?? [] as $number) { + $types[] = self::getTypeLooselyEqualTo($number, $phpVersion); + } + if ($this->getNumberValues($this->numbers) === null) { + return new NeverType(); + } + } + foreach ($this->constantStrings as $constantString) { + $types[] = self::getTypeLooselyEqualTo($constantString, $phpVersion); + } + if ($this->emptyArray) { + $types[] = TypeCombinator::union(new NullType(), new ConstantBooleanType(false), new ConstantArrayType([], [])); + } + if ($this->objects !== null) { + $enumCases = $this->objects->isEnum()->yes() ? $this->objects->getEnumCases() : []; + if (count($enumCases) !== 1) { + return new NeverType(); + } + + $types[] = TypeCombinator::union($enumCases[0], new ConstantBooleanType(true)); + } + + // one value at a time: intersecting all the unions at once multiplies them out + $alwaysEqualType = null; + foreach ($types as $type) { + $alwaysEqualType = $alwaysEqualType === null ? $type : TypeCombinator::intersect($alwaysEqualType, $type); + if ($alwaysEqualType instanceof NeverType) { + break; + } + } + + return $alwaysEqualType ?? new NeverType(); + } + + private static function unionTypes(?Type $left, ?Type $right): ?Type + { + if ($left === null || $right === null) { + return $left ?? $right; + } + + return TypeCombinator::union($left, $right); + } + + /** + * The numbers of $numbers when there are finitely many of them. + * + * @return list|null + */ + private function getNumberValues(NumberSet $numbers): ?array + { + $values = []; + foreach ($numbers->getIntegerIntervals() as [$min, $max]) { + if ($min === null || $min !== $max) { + return null; + } + + $values[] = $min; + } + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + if ($min !== $max) { + return null; + } + + $values[] = $min; + } + if ($numbers->hasNan()) { + $values[] = NAN; + } + + return $values; + } + + /** + * The values that loosely equal $value, of the kinds a type can describe: null and the + * booleans by its truthiness, the numbers equal to its number, and its string forms. + */ + private static function getTypeLooselyEqualTo(bool|float|int|string|null $value, PhpVersion $phpVersion): Type + { + // null compares with a string as with '', with anything else by truthiness, like the booleans + $types = [new ConstantBooleanType((bool) $value)]; + if (is_string($value) ? $value === '' : !(bool) $value) { + $types[] = new NullType(); + } + if ($value === null) { + // and with an array by its emptiness + $types[] = new ConstantArrayType([], []); + } + + $number = self::getLooselyEqualNumber($value, $phpVersion); + if ($number !== null) { + // both zeros + $types[] = FloatRangeType::fromInterval((float) $number, (float) $number); + if (is_int($number)) { + $types[] = new ConstantIntegerType($number); + } elseif (floor($number) === $number && abs($number) <= self::EXACT_FLOAT_INTEGER_LIMIT) { + $types[] = new ConstantIntegerType((int) $number); + } + } + + // the string itself, the number's own string, and the strings that are falsy + $strings = ['', '0']; + if ($number !== null) { + $strings[] = self::getNumberString($number); + } + if (is_string($value)) { + $strings[] = $value; + } + foreach ($strings as $string) { + if (LooseComparisonHelper::compareValues($value, $string, $phpVersion) !== 0) { + continue; + } + + $types[] = new ConstantStringType($string); + } + + return TypeCombinator::union(...$types); + } + + /** + * The number every number loosely equal to $value equals, or null when no number, + * or more than one, equals it. + */ + private static function getLooselyEqualNumber(bool|float|int|string|null $value, PhpVersion $phpVersion): int|float|null + { + if ($value === null) { + return 0; + } + if (is_bool($value) || (is_float($value) && is_nan($value))) { + return null; + } + if (!is_string($value)) { + return $value; + } + + $number = LooseComparisonHelper::parseNumericString($value, $phpVersion); + if ($number !== null) { + return $number[0]; + } + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + // the number the string starts with + return LooseComparisonHelper::parseNumericPrefix($value) ?? 0; + } + + // a number compares with a non-numeric string as its own string + foreach ([INF, -INF] as $infinity) { + if (self::getNumberString($infinity) === $value) { + return $infinity; + } + } + + return null; + } + + /** + * The shortest string that reads back as $number, independent of the precision ini settings. + */ + private static function getNumberString(int|float $number): string + { + if (is_int($number)) { + return (string) $number; + } + if (is_infinite($number)) { + return $number > 0 ? 'INF' : '-INF'; + } + + $string = sprintf('%.' . self::SHORT_FLOAT_DIGITS . 'G', $number); + if ((float) $string === $number) { + return $string; + } + + return sprintf('%.' . self::EXACT_FLOAT_DIGITS . 'G', $number); + } + + /** + * The numbers, numeric strings, objects and resources that can equal a number of + * $numbers. Booleans and null are left out, NAN equals none of them. + */ + private static function getNumberPartnerType(NumberSet $numbers, PhpVersion $phpVersion): Type + { + // an integer and a float are equal when the integer converted to float is the float + $integerTypes = []; + $floatIntervals = []; + foreach ($numbers->getIntegerIntervals() as [$min, $max]) { + $integerTypes[] = IntegerRangeType::fromInterval($min, $max); + $floatIntervals[] = [(float) ($min ?? PHP_INT_MIN), (float) ($max ?? PHP_INT_MAX)]; + } + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + $integerTypes[] = self::getIntegerTypeInInterval($min, $max); + $floatIntervals[] = [$min, $max]; + } + if ($floatIntervals === []) { + return new NeverType(); + } + + $integerType = TypeCombinator::union(...$integerTypes); + $types = [$integerType, self::createNumericStringType()]; + $containsOne = false; + $containsInfinity = false; + $containsNegativeInfinity = false; + foreach ($floatIntervals as [$min, $max]) { + $types[] = FloatRangeType::fromInterval($min, $max); + $containsOne = $containsOne || ($min <= 1.0 && $max >= 1.0); + $containsInfinity = $containsInfinity || $max === INF; + $containsNegativeInfinity = $containsNegativeInfinity || $min === -INF; + } + + // an object compares as 1, unless it is number-like + $types[] = $containsOne ? new ObjectWithoutClassType() : self::createNumberLikeObjectType($phpVersion); + if (!TypeCombinator::intersect($integerType, IntegerRangeType::fromInterval(1, null))->isInteger()->no()) { + // as its integer ID + $types[] = new ResourceType(); + } + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + // as the number the string starts with + $types[] = new StringType(); + } else { + // as its own string + if ($containsInfinity) { + $types[] = new ConstantStringType('INF'); + } + if ($containsNegativeInfinity) { + $types[] = new ConstantStringType('-INF'); + } + } + + return TypeCombinator::union(...$types); + } + + private static function canBeZero(NumberSet $numbers): bool + { + foreach ($numbers->getIntegerIntervals() as [$min, $max]) { + if (($min ?? PHP_INT_MIN) <= 0 && ($max ?? PHP_INT_MAX) >= 0) { + return true; + } + } + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + if ($min <= 0.0 && $max >= 0.0) { + return true; + } + } + + return false; + } + + private static function canBeNonZero(NumberSet $numbers): bool + { + if ($numbers->hasNan()) { + return true; + } + foreach ([...$numbers->getIntegerIntervals(), ...$numbers->getFloatIntervals()] as [$min, $max]) { + if ($min === null || $min !== $max || (float) $min !== 0.0) { + return true; + } + } + + return false; + } + + private static function getBooleanAndNullPartnerType(bool $canBeZero, bool $canBeNonZero): Type + { + $types = []; + if ($canBeZero) { + $types[] = new NullType(); + $types[] = new ConstantBooleanType(false); + } + if ($canBeNonZero) { + $types[] = new ConstantBooleanType(true); + } + + return TypeCombinator::union(...$types); + } + + private static function getConstantStringPartnerType(string $value, PhpVersion $phpVersion): Type + { + $types = [ + new ConstantStringType($value), + self::createStringableObjectType($phpVersion), + self::getBooleanAndNullPartnerType(false, $value !== '' && $value !== '0'), + self::getTypeLooselyEqualTo($value, $phpVersion), + ]; + + $number = LooseComparisonHelper::parseNumericString($value, $phpVersion); + if ($number !== null) { + $number = $number[0]; + $types[] = self::createNumericStringType(); + $types[] = self::createNumberType($number); + if ($number >= 1 && floor($number) === (float) $number) { + $types[] = new ResourceType(); + } + } elseif (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + $number = LooseComparisonHelper::parseNumericPrefix($value) ?? 0; + $types[] = self::createNumberType($number); + if ($number >= 1 && floor($number) === (float) $number) { + $types[] = new ResourceType(); + } + } + + return TypeCombinator::union(...$types); + } + + private static function getStringPartnerType(Type $strings, PhpVersion $phpVersion): Type + { + // a string compares with null as with '', and with a bool by its truthiness + $booleanAndNull = [new ConstantBooleanType(true)]; + if (!$strings->isNonEmptyString()->yes()) { + $booleanAndNull[] = new NullType(); + } + if (!$strings->isNonFalsyString()->yes()) { + $booleanAndNull[] = new ConstantBooleanType(false); + } + $booleanAndNull = TypeCombinator::union(...$booleanAndNull); + + if ($strings->isNumericString()->yes()) { + // a numeric string compares with a non-numeric one byte by byte, so they differ + return TypeCombinator::union( + new IntegerType(), + FloatRangeType::fromInterval(-INF, INF), + self::createNumericStringType(), + self::createStringableObjectType($phpVersion), + new ResourceType(), + $booleanAndNull, + ); + } + + // a non-numeric string equals only itself + return TypeCombinator::union( + $strings, + $strings->isNumericString()->no() ? new NeverType() : self::createNumericStringType(), + new IntegerType(), + new FloatType(), + self::createStringableObjectType($phpVersion), + new ResourceType(), + $booleanAndNull, + ); + } + + private static function getArrayPartnerType(bool $empty, bool $nonEmpty): Type + { + if (!$nonEmpty) { + return TypeCombinator::union(new NullType(), new ConstantBooleanType(false), new ConstantArrayType([], [])); + } + + $arrayType = new ArrayType(new MixedType(), new MixedType()); + if (!$empty) { + return TypeCombinator::union(new ConstantBooleanType(true), TypeCombinator::intersect($arrayType, new NonEmptyArrayType())); + } + + return TypeCombinator::union($arrayType, new BooleanType(), new NullType()); + } + + /** The integers and floats equal to $number. */ + private static function createNumberType(int|float $number): Type + { + $float = (float) $number; + $integerType = is_int($number) ? new ConstantIntegerType($number) : self::getIntegerTypeInInterval($float, $float); + + return TypeCombinator::union($integerType, FloatRangeType::fromInterval($float, $float)); + } + + /** The integers that compare equal to a float of the interval. */ + private static function getIntegerTypeInInterval(float $min, float $max): Type + { + if ($min > $max || $min > (float) PHP_INT_MAX || $max < (float) PHP_INT_MIN) { + return new NeverType(); + } + + if ($min < -self::EXACT_FLOAT_INTEGER_LIMIT) { + $integerMin = null; + } elseif ($min > self::EXACT_FLOAT_INTEGER_LIMIT) { + $integerMin = self::EXACT_FLOAT_INTEGER_LIMIT; + } else { + $integerMin = (int) ceil($min); + } + + if ($max > self::EXACT_FLOAT_INTEGER_LIMIT) { + $integerMax = null; + } elseif ($max < -self::EXACT_FLOAT_INTEGER_LIMIT) { + $integerMax = -self::EXACT_FLOAT_INTEGER_LIMIT; + } else { + $integerMax = (int) floor($max); + } + + return IntegerRangeType::fromInterval($integerMin, $integerMax); + } + + /** GMP, BcMath\Number (since PHP 8.4) and SimpleXMLElement objects compare as their number, and can be falsy. */ + private static function createNumberLikeObjectType(PhpVersion $phpVersion): Type + { + $types = [new ObjectType('GMP'), new ObjectType('SimpleXMLElement')]; + if ($phpVersion->getVersionId() >= 80400) { + $types[] = new ObjectType('BcMath\\Number'); + } + + return TypeCombinator::union(...$types); + } + + /** The objects that compare with a string as their string. */ + private static function createStringableObjectType(PhpVersion $phpVersion): Type + { + // a class with __toString() implements Stringable since PHP 8.0 + if ($phpVersion->getVersionId() < 80000) { + return new ObjectWithoutClassType(); + } + + return TypeCombinator::union(new ObjectType('Stringable'), new ObjectType('GMP')); + } + + private static function createNumericStringType(): Type + { + return new IntersectionType([new StringType(), new AccessoryNumericStringType()]); + } + +} diff --git a/turbo-ext/src/AccessoryArrayListType.cpp b/turbo-ext/src/AccessoryArrayListType.cpp index d6d1adda1a6..da5c5c0eb0d 100644 --- a/turbo-ext/src/AccessoryArrayListType.cpp +++ b/turbo-ext/src/AccessoryArrayListType.cpp @@ -359,6 +359,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_array_list_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryArrayListType"); ptdecl::AccessoryArrayListType::declareClass(cls); ptdecl::AccessoryArrayListType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS(true, true)); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp b/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp index 434de3ca46f..52b7dfae2aa 100644 --- a/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp +++ b/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp @@ -557,6 +557,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_decimal_integer_string_type) ptdecl::AccessoryDecimalIntegerStringType::declareClass(cls); /* "inverse" must stay the first declared property (slots::inverse) */ ptdecl::AccessoryDecimalIntegerStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { bool inverse = false; diff --git a/turbo-ext/src/AccessoryLiteralStringType.cpp b/turbo-ext/src/AccessoryLiteralStringType.cpp index fbb84361776..43d0a400fec 100644 --- a/turbo-ext/src/AccessoryLiteralStringType.cpp +++ b/turbo-ext/src/AccessoryLiteralStringType.cpp @@ -307,6 +307,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_literal_string_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryLiteralStringType"); ptdecl::AccessoryLiteralStringType::declareClass(cls); ptdecl::AccessoryLiteralStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/AccessoryLowercaseStringType.cpp b/turbo-ext/src/AccessoryLowercaseStringType.cpp index b811672f5c7..caa9b7b49d1 100644 --- a/turbo-ext/src/AccessoryLowercaseStringType.cpp +++ b/turbo-ext/src/AccessoryLowercaseStringType.cpp @@ -334,6 +334,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_lowercase_string_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryLowercaseStringType"); ptdecl::AccessoryLowercaseStringType::declareClass(cls); ptdecl::AccessoryLowercaseStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/AccessoryNonEmptyStringType.cpp b/turbo-ext/src/AccessoryNonEmptyStringType.cpp index 889dd87c394..da0b0eb7a6b 100644 --- a/turbo-ext/src/AccessoryNonEmptyStringType.cpp +++ b/turbo-ext/src/AccessoryNonEmptyStringType.cpp @@ -332,6 +332,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_non_empty_string_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryNonEmptyStringType"); ptdecl::AccessoryNonEmptyStringType::declareClass(cls); ptdecl::AccessoryNonEmptyStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/AccessoryNonFalsyStringType.cpp b/turbo-ext/src/AccessoryNonFalsyStringType.cpp index 537aed2e734..f96bdf5b3d4 100644 --- a/turbo-ext/src/AccessoryNonFalsyStringType.cpp +++ b/turbo-ext/src/AccessoryNonFalsyStringType.cpp @@ -329,6 +329,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_non_falsy_string_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryNonFalsyStringType"); ptdecl::AccessoryNonFalsyStringType::declareClass(cls); ptdecl::AccessoryNonFalsyStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/AccessoryNumericStringType.cpp b/turbo-ext/src/AccessoryNumericStringType.cpp index 520c76bd4ca..a9b3ea04d01 100644 --- a/turbo-ext/src/AccessoryNumericStringType.cpp +++ b/turbo-ext/src/AccessoryNumericStringType.cpp @@ -381,6 +381,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_numeric_string_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryNumericStringType"); ptdecl::AccessoryNumericStringType::declareClass(cls); ptdecl::AccessoryNumericStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/AccessoryUppercaseStringType.cpp b/turbo-ext/src/AccessoryUppercaseStringType.cpp index 8feeae369ee..0a928adeb21 100644 --- a/turbo-ext/src/AccessoryUppercaseStringType.cpp +++ b/turbo-ext/src/AccessoryUppercaseStringType.cpp @@ -334,6 +334,8 @@ PT_MINIT_REGISTRATION(pt_register_accessory_uppercase_string_type) reg::Class cls("PHPStan\\Type\\Accessory\\AccessoryUppercaseStringType"); ptdecl::AccessoryUppercaseStringType::declareClass(cls); ptdecl::AccessoryUppercaseStringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/BooleanType.cpp b/turbo-ext/src/BooleanType.cpp index 2399f19f1a5..fa5b18bb3bd 100644 --- a/turbo-ext/src/BooleanType.cpp +++ b/turbo-ext/src/BooleanType.cpp @@ -238,6 +238,12 @@ PT_MINIT_REGISTRATION(pt_register_boolean_type) reg::Class cls("PHPStan\\Type\\BooleanType"); ptdecl::BooleanType::declareClass(cls); ptdecl::BooleanType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + zv::Args args{true, true}; + PT_RETURN_VAL(pt_value_set_create(PT_LC("createbooleans"), 2, args)); + }); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/ClosureType.cpp b/turbo-ext/src/ClosureType.cpp index a69cb859497..ddffba05c98 100644 --- a/turbo-ext/src/ClosureType.cpp +++ b/turbo-ext/src/ClosureType.cpp @@ -946,6 +946,8 @@ PT_MINIT_REGISTRATION(pt_register_closure_type) ptdecl::ClosureType::declareClass(cls); /* the slots must stay in this order (PT_CLT_PROP_*) */ ptdecl::ClosureType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createobjects")); cls.method("__construct", reg::Public, 0, { parametersArg, diff --git a/turbo-ext/src/ConstantBooleanType.cpp b/turbo-ext/src/ConstantBooleanType.cpp index 0793584944c..eaa4a2dc93a 100644 --- a/turbo-ext/src/ConstantBooleanType.cpp +++ b/turbo-ext/src/ConstantBooleanType.cpp @@ -264,6 +264,15 @@ PT_MINIT_REGISTRATION(pt_register_constant_boolean_type) ptdecl::ConstantBooleanType::declareClass(cls); /* "value" must stay the first declared property (slots::value) */ ptdecl::ConstantBooleanType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + zv::Val value = pt_type_call(Z_OBJ_P(ZEND_THIS), PT_LC("getvalue"), 0, NULL); + if (UNEXPECTED(value.isUndef())) RETURN_THROWS(); + bool isTrue = Z_TYPE_P(value.raw()) == IS_TRUE; + zv::Args args{!isTrue, isTrue}; + PT_RETURN_VAL(pt_value_set_create(PT_LC("createbooleans"), 2, args)); + }); cls.method<&ConstantBooleanType::construct, zp::Bool>(sigs::__construct); diff --git a/turbo-ext/src/ConstantStringType.cpp b/turbo-ext/src/ConstantStringType.cpp index fe0955e5409..ed067e8881a 100644 --- a/turbo-ext/src/ConstantStringType.cpp +++ b/turbo-ext/src/ConstantStringType.cpp @@ -1336,6 +1336,13 @@ PT_MINIT_REGISTRATION(pt_register_constant_string_type) reg::Class cls("PHPStan\\Type\\Constant\\ConstantStringType"); ptdecl::ConstantStringType::declareClass(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + zv::Val value = pt_type_call(Z_OBJ_P(ZEND_THIS), PT_LC("getvalue"), 0, NULL); + if (UNEXPECTED(value.isUndef())) RETURN_THROWS(); + PT_RETURN_VAL(pt_value_set_create(PT_LC("createconstantstring"), 1, value.raw())); + }); cls.privateClassConstantLong("DESCRIBE_LIMIT", PT_CST_DESCRIBE_LIMIT); /* the declaration order defines the PT_CST_PROP_* slots */ cls.privateTypedClassPropertyDefaultNull("objectType", "PHPStan\\Type\\ObjectType"); diff --git a/turbo-ext/src/FloatType.cpp b/turbo-ext/src/FloatType.cpp index fa4ffd86c02..244716075bd 100644 --- a/turbo-ext/src/FloatType.cpp +++ b/turbo-ext/src/FloatType.cpp @@ -271,6 +271,8 @@ PT_MINIT_REGISTRATION(pt_register_float_type) reg::Class cls("PHPStan\\Type\\FloatType"); ptdecl::FloatType::declareClass(cls); ptdecl::FloatType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, pt_value_set_of_numbers); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/IntegerRangeType.cpp b/turbo-ext/src/IntegerRangeType.cpp index 2c2a4a200a9..98308b819af 100644 --- a/turbo-ext/src/IntegerRangeType.cpp +++ b/turbo-ext/src/IntegerRangeType.cpp @@ -1360,6 +1360,8 @@ PT_MINIT_REGISTRATION(pt_register_integer_range_type) /* "min" and "max" must stay the first two declared properties * (slots::min, slots::max) */ ptdecl::IntegerRangeType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, pt_value_set_of_numbers); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { zend_long min, max; diff --git a/turbo-ext/src/IntegerType.cpp b/turbo-ext/src/IntegerType.cpp index 2cca1c6dea2..1c6fbea304f 100644 --- a/turbo-ext/src/IntegerType.cpp +++ b/turbo-ext/src/IntegerType.cpp @@ -250,6 +250,8 @@ PT_MINIT_REGISTRATION(pt_register_integer_type) reg::Class cls("PHPStan\\Type\\IntegerType"); ptdecl::IntegerType::declareClass(cls); ptdecl::IntegerType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, pt_value_set_of_numbers); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/IntersectionType.cpp b/turbo-ext/src/IntersectionType.cpp index d739200bd01..1a0eafae53a 100644 --- a/turbo-ext/src/IntersectionType.cpp +++ b/turbo-ext/src/IntersectionType.cpp @@ -3213,6 +3213,8 @@ PT_MINIT_REGISTRATION(pt_register_intersection_type) { reg::Class cls("PHPStan\\Type\\IntersectionType"); ptdecl::IntersectionType::declareClass(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { pt_value_set_of_members(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); }); /* the slots PT_IT_PROP_*: the sixteen explicit properties first, the * promoted $types after them */ cls.privateTypedPropertyDefaultNull("sortedTypesCache", MAY_BE_ARRAY); diff --git a/turbo-ext/src/NeverType.cpp b/turbo-ext/src/NeverType.cpp index ab3adfeb9aa..16917c07da2 100644 --- a/turbo-ext/src/NeverType.cpp +++ b/turbo-ext/src/NeverType.cpp @@ -283,6 +283,8 @@ PT_MINIT_REGISTRATION(pt_register_never_type) /* "isExplicit" and "reason" must stay the first two declared properties * (slots::isExplicit, slots::reason) */ ptdecl::NeverType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createempty")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { bool isExplicit = false; diff --git a/turbo-ext/src/NonEmptyArrayType.cpp b/turbo-ext/src/NonEmptyArrayType.cpp index 27dffdf638a..41f52528a58 100644 --- a/turbo-ext/src/NonEmptyArrayType.cpp +++ b/turbo-ext/src/NonEmptyArrayType.cpp @@ -316,6 +316,8 @@ PT_MINIT_REGISTRATION(pt_register_non_empty_array_type) reg::Class cls("PHPStan\\Type\\Accessory\\NonEmptyArrayType"); ptdecl::NonEmptyArrayType::declareClass(cls); ptdecl::NonEmptyArrayType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS(false, true)); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/NullType.cpp b/turbo-ext/src/NullType.cpp index d58ecaf4893..62aed52a7dd 100644 --- a/turbo-ext/src/NullType.cpp +++ b/turbo-ext/src/NullType.cpp @@ -408,6 +408,8 @@ PT_MINIT_REGISTRATION(pt_register_null_type) reg::Class cls("PHPStan\\Type\\NullType"); ptdecl::NullType::declareClass(cls); ptdecl::NullType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createnull")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp index 57350981b07..488e3cc7b2d 100644 --- a/turbo-ext/src/NumberSet.cpp +++ b/turbo-ext/src/NumberSet.cpp @@ -299,8 +299,8 @@ class NumberSetMath { NumberSetData hull; if (!set.integerIntervals.empty()) { - NullableLong min = set.integerIntervals[0].min; - NullableLong max = set.integerIntervals[0].max; + phpstanturbo::NullableLong min = set.integerIntervals[0].min; + phpstanturbo::NullableLong max = set.integerIntervals[0].max; for (const NumberSetData::IntegerInterval &interval : set.integerIntervals) { min = min.isNull || interval.min.isNull ? NullableLong::null() : NullableLong::of(interval.min.value < min.value ? interval.min.value : min.value); max = max.isNull || interval.max.isNull ? NullableLong::null() : NullableLong::of(interval.max.value > max.value ? interval.max.value : max.value); @@ -768,6 +768,48 @@ bool pt_type_to_number_set(zval *type, NumberSetData &out, bool &has) #define PT_THIS NumberSet(Z_OBJ_P(ZEND_THIS)) +/* union() / intersect(): (self $other) — PHP's max()/min() keep the first of + * equal values, as `b > a ? b : a` does */ +template +static void pt_ns_combine(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *other; + if (!zp::parse(execute_data, other)) RETURN_THROWS(); + if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(other), pt_ce_number_set))) { + zend_type_error("%s::%s(): Argument #1 ($other) must be of type %s, %s given", ZSTR_VAL(pt_ce_number_set->name), get_active_function_name(), ZSTR_VAL(pt_ce_number_set->name), zend_zval_value_name(other)); + RETURN_THROWS(); + } + NumberSetData left, right; + if (UNEXPECTED(!PT_THIS.read(left) || !NumberSet(Z_OBJ_P(other)).read(right))) RETURN_THROWS(); + NumberSetData result; + if (!Intersect) { + result.integerIntervals = left.integerIntervals; + result.integerIntervals.insert(result.integerIntervals.end(), right.integerIntervals.begin(), right.integerIntervals.end()); + result.floatIntervals = left.floatIntervals; + result.floatIntervals.insert(result.floatIntervals.end(), right.floatIntervals.begin(), right.floatIntervals.end()); + result.nan = left.nan || right.nan; + PT_RETURN_VAL(pt_number_set_new(result)); + } + for (const auto &l : left.integerIntervals) { + for (const auto &r : right.integerIntervals) { + phpstanturbo::NullableLong min = l.min.isNull || r.min.isNull ? (l.min.isNull ? r.min : l.min) : phpstanturbo::NullableLong::of(r.min.value > l.min.value ? r.min.value : l.min.value); + phpstanturbo::NullableLong max = l.max.isNull || r.max.isNull ? (l.max.isNull ? r.max : l.max) : phpstanturbo::NullableLong::of(r.max.value < l.max.value ? r.max.value : l.max.value); + if (!min.isNull && !max.isNull && min.value > max.value) continue; + result.integerIntervals.push_back({min, max}); + } + } + for (const auto &l : left.floatIntervals) { + for (const auto &r : right.floatIntervals) { + double min = r.min > l.min ? r.min : l.min; + double max = r.max < l.max ? r.max : l.max; + if (min > max) continue; + result.floatIntervals.push_back({min, max}); + } + } + result.nan = left.nan && right.nan; + PT_RETURN_VAL(pt_number_set_new(result)); +} + /* add() & co.: (self $right, PhpVersion $phpVersion) */ template static void pt_ns_operator(INTERNAL_FUNCTION_PARAMETERS) @@ -810,6 +852,8 @@ PT_MINIT_REGISTRATION(pt_register_number_set) ZEND_PARSE_PARAMETERS_NONE(); PT_RETURN_VAL(PT_THIS.get(slots::nan, "nan")); }); + cls.method(sigs::union_, pt_ns_combine); + cls.method(sigs::intersect, pt_ns_combine); cls.method(sigs::add, pt_ns_operator); cls.method(sigs::subtract, pt_ns_operator); cls.method(sigs::multiply, pt_ns_operator); diff --git a/turbo-ext/src/ObjectShapeType.cpp b/turbo-ext/src/ObjectShapeType.cpp index cc4cfb0d15a..898ac1fefaf 100644 --- a/turbo-ext/src/ObjectShapeType.cpp +++ b/turbo-ext/src/ObjectShapeType.cpp @@ -1261,6 +1261,8 @@ PT_MINIT_REGISTRATION(pt_register_object_shape_type) /* "properties" and "optionalProperties" must stay the first two * declared properties (slots::properties, slots::optionalProperties) */ ptdecl::ObjectShapeType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createobjects")); cls.method<&ObjectShapeType::construct, zp::Arr, zp::Arr>(sigs::__construct); diff --git a/turbo-ext/src/ObjectType.cpp b/turbo-ext/src/ObjectType.cpp index 90357537f03..00372680ea3 100644 --- a/turbo-ext/src/ObjectType.cpp +++ b/turbo-ext/src/ObjectType.cpp @@ -3625,6 +3625,8 @@ PT_MINIT_REGISTRATION(pt_register_object_type) reg::Class cls("PHPStan\\Type\\ObjectType"); ptdecl::ObjectType::declareClass(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createobjects")); /* the twin's declaration order defines the PT_OT_PROP_* slots */ cls.privateClassConstantLong("DESCRIPTION_CACHE_LIMIT", PT_OT_DESCRIPTION_CACHE_LIMIT); cls.privateClassConstantValue("EXTRA_OFFSET_CLASSES", pt_ot_extra_offset_classes_constant); diff --git a/turbo-ext/src/ObjectWithoutClassType.cpp b/turbo-ext/src/ObjectWithoutClassType.cpp index 1663cedb5ea..acea1c74875 100644 --- a/turbo-ext/src/ObjectWithoutClassType.cpp +++ b/turbo-ext/src/ObjectWithoutClassType.cpp @@ -368,6 +368,8 @@ PT_MINIT_REGISTRATION(pt_register_object_without_class_type) ptdecl::ObjectWithoutClassType::declareClass(cls); /* "subtractedType" must stay the first declared property (slots::subtractedType) */ ptdecl::ObjectWithoutClassType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createobjects")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { zval *subtractedType = NULL; diff --git a/turbo-ext/src/OversizedArrayType.cpp b/turbo-ext/src/OversizedArrayType.cpp index 6002aee9578..5d751e8a366 100644 --- a/turbo-ext/src/OversizedArrayType.cpp +++ b/turbo-ext/src/OversizedArrayType.cpp @@ -272,6 +272,8 @@ PT_MINIT_REGISTRATION(pt_register_oversized_array_type) reg::Class cls("PHPStan\\Type\\Accessory\\OversizedArrayType"); ptdecl::OversizedArrayType::declareClass(cls); ptdecl::OversizedArrayType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS(false, true)); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/ResourceType.cpp b/turbo-ext/src/ResourceType.cpp index 9c74dd4edb4..5750f379509 100644 --- a/turbo-ext/src/ResourceType.cpp +++ b/turbo-ext/src/ResourceType.cpp @@ -54,6 +54,8 @@ PT_MINIT_REGISTRATION(pt_register_resource_type) reg::Class cls("PHPStan\\Type\\ResourceType"); ptdecl::ResourceType::declareClass(cls); ptdecl::ResourceType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createresources")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/StaticType.cpp b/turbo-ext/src/StaticType.cpp index 4db22723232..7efab4d0904 100644 --- a/turbo-ext/src/StaticType.cpp +++ b/turbo-ext/src/StaticType.cpp @@ -984,6 +984,8 @@ PT_MINIT_REGISTRATION(pt_register_static_type) reg::Class cls("PHPStan\\Type\\StaticType"); ptdecl::StaticType::declareClass(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createobjects")); /* the slots must stay in this order (PT_ST_PROP_*) */ cls.privateTypedClassProperty("subtractedType", "PHPStan\\Type\\Type", true); cls.privateTypedClassPropertyDefaultNull("staticObjectType", "PHPStan\\Type\\ObjectType"); diff --git a/turbo-ext/src/StringType.cpp b/turbo-ext/src/StringType.cpp index efb8d06e9fb..01aba3a6917 100644 --- a/turbo-ext/src/StringType.cpp +++ b/turbo-ext/src/StringType.cpp @@ -418,6 +418,8 @@ PT_MINIT_REGISTRATION(pt_register_string_type) reg::Class cls("PHPStan\\Type\\StringType"); ptdecl::StringType::declareClass(cls); ptdecl::StringType::declareProperties(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createstrings")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index 7a0484f0482..06e79bbb4a1 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -165,6 +165,44 @@ zv::Val pt_type_call_static(int classIdx, const char *lcname, size_t len, uint32 return pt_type_call_fn(fn, NULL, ce, argc, argv); } +zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc, zval *argv) +{ + return pt_type_call_static(PT_CLASS_VALUE_SET, lcFactory, len, argc, argv); +} + +void pt_value_set_of_this(INTERNAL_FUNCTION_PARAMETERS, const char *lcFactory, size_t len) +{ + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(pt_value_set_create(lcFactory, len, 1, ZEND_THIS)); +} + +void pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS) +{ + ZEND_PARSE_PARAMETERS_NONE(); + zv::Val numbers = pt_type_call(Z_OBJ_P(ZEND_THIS), PT_LC("tonumberset"), 0, NULL); + if (UNEXPECTED(numbers.isUndef())) RETURN_THROWS(); + PT_RETURN_VAL(pt_value_set_create(PT_LC("createnumbers"), 1, numbers.raw())); +} + +void pt_value_set_of_members(INTERNAL_FUNCTION_PARAMETERS, bool intersect) +{ + ZEND_PARSE_PARAMETERS_NONE(); + zv::Val types = pt_type_call(Z_OBJ_P(ZEND_THIS), PT_LC("gettypes"), 0, NULL); + if (UNEXPECTED(types.isUndef())) RETURN_THROWS(); + zv::Val valueSet = intersect ? pt_value_set_create(PT_LC("createunknown")) : pt_value_set_create(PT_LC("createempty")); + if (UNEXPECTED(valueSet.isUndef())) RETURN_THROWS(); + zval *type; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(types.raw()), type) { + zv::Val memberSet = pt_type_call(Z_OBJ_P(type), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(memberSet.isUndef())) RETURN_THROWS(); + valueSet = intersect + ? pt_type_call(Z_OBJ_P(valueSet.raw()), PT_LC("intersect"), 1, memberSet.raw()) + : pt_type_call(Z_OBJ_P(valueSet.raw()), PT_LC("union"), 1, memberSet.raw()); + if (UNEXPECTED(valueSet.isUndef())) RETURN_THROWS(); + } ZEND_HASH_FOREACH_END(); + PT_RETURN_VAL(std::move(valueSet)); +} + zv::Val pt_type_new(int classIdx, uint32_t argc, zval *argv) { zend_class_entry *ce = pt_class(classIdx); @@ -2271,6 +2309,14 @@ static void ZEND_FASTCALL arrayTraitChunkArray(INTERNAL_FUNCTION_PARAMETERS) void pt_type_trait_array(reg::Class &cls) { namespace sigs = ptdecl::ArrayTypeTrait::sig; + /* ValueSet::createArrays(!$this->isIterableAtLeastOnce()->yes(), !...->no()) */ + cls.traitMethod(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + zend_long nonEmpty = pt_type_call_trinary(PT_THIS_OBJ, PT_LC("isiterableatleastonce"), 0, NULL); + if (UNEXPECTED(nonEmpty < 0)) RETURN_THROWS(); + zv::Args args{nonEmpty != PT_TRI_YES, nonEmpty != PT_TRI_NO}; + PT_RETURN_VAL(pt_value_set_create(PT_LC("createarrays"), 2, args)); + }); cls.traitMethod(sigs::isArray, trinaryYes0); cls.traitOp(PT_OP_IS_ARRAY, PT_OP_LAMBDA { return pt_op_trinary(PT_TRI_YES); }); cls.traitMethod(sigs::toArray, this0); @@ -3729,6 +3775,7 @@ void pt_type_trait_late_resolvable(reg::Class &cls) * own properties as PHP binds a trait's */ cls.privateTypedClassPropertyDefaultNull("result", ptcls::type); + cls.traitMethod(sigs::toValueSet, lrDelegate); cls.traitMethod(sigs::getObjectClassNames, lrDelegate); cls.traitMethod(sigs::getObjectClassReflections, lrDelegate); cls.traitOp(PT_OP_GET_OBJECT_CLASS_REFLECTIONS, PT_OP_LAMBDA { return LateResolvable(self, scope).delegateOp(PT_OP_GET_OBJECT_CLASS_REFLECTIONS, argc, argv); }); @@ -5461,6 +5508,7 @@ void pt_type_trait_operator(reg::Class &cls) ZEND_PARSE_PARAMETERS_NONE(); RETURN_NULL(); }); + cls.traitMethod(sigs::toValueSet, PT_VALUE_SET_CREATE("createunknown")); cls.traitMethod(sigs::arithmeticAdd, operatorBinary); cls.traitMethod(sigs::arithmeticSubtract, operatorBinary); cls.traitMethod(sigs::arithmeticMultiply, operatorBinary); @@ -5479,19 +5527,21 @@ void pt_type_trait_operator(reg::Class &cls) cls.traitMethod(sigs::bitwiseShiftRight, operatorBinary); cls.traitMethod(sigs::stringConcatenate, operatorBinary); cls.traitMethod(sigs::arrayUnion, operatorBinary); - /* LooseComparisonHelper::getPossiblyLooselyEqualType($this, $phpVersion) */ + /* $this->toValueSet()->getPossiblyLooselyEqualType($phpVersion) */ cls.traitMethod(sigs::getPossiblyLooselyEqualType, [](INTERNAL_FUNCTION_PARAMETERS) { zval *phpVersion; if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); - zv::Args args{ZEND_THIS, phpVersion}; - PT_RETURN_VAL(pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getpossiblylooselyequaltype"), 2, args)); + zv::Val valueSet = pt_type_call(PT_THIS_OBJ, PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(valueSet.isUndef())) RETURN_THROWS(); + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(valueSet.raw()), PT_LC("getpossiblylooselyequaltype"), 1, phpVersion)); }); - /* LooseComparisonHelper::getAlwaysLooselyEqualType($this, $phpVersion) */ + /* $this->toValueSet()->getAlwaysLooselyEqualType($phpVersion) */ cls.traitMethod(sigs::getAlwaysLooselyEqualType, [](INTERNAL_FUNCTION_PARAMETERS) { zval *phpVersion; if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); - zv::Args args{ZEND_THIS, phpVersion}; - PT_RETURN_VAL(pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getalwayslooselyequaltype"), 2, args)); + zv::Val valueSet = pt_type_call(PT_THIS_OBJ, PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(valueSet.isUndef())) RETURN_THROWS(); + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(valueSet.raw()), PT_LC("getalwayslooselyequaltype"), 1, phpVersion)); }); } diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 6cc0907b09b..a19c98b0be8 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -170,6 +170,40 @@ zv::Val pt_type_call_static(int classIdx, const char *lcname, size_t len, uint32 * declares one; UNDEF = pending exception */ zv::Val pt_type_new(int classIdx, uint32_t argc, zval *argv); +/* {{{ Type::toValueSet() (ValueSet.php stays PHP) */ + +/* ValueSet::(...$args); UNDEF = pending exception */ +zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc = 0, zval *argv = NULL); + +/* ValueSet::($this): the strings, objects or numbers of a type + * describing itself */ +void pt_value_set_of_this(INTERNAL_FUNCTION_PARAMETERS, const char *lcFactory, size_t len); + +/* ValueSet::createNumbers($this->toNumberSet()) */ +void pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS); + +/* the union (UnionType) or the intersection (IntersectionType) of the sets of + * $this->getTypes() */ +void pt_value_set_of_members(INTERNAL_FUNCTION_PARAMETERS, bool intersect); + +#define PT_VALUE_SET_OF_THIS(factory) \ + [](INTERNAL_FUNCTION_PARAMETERS) { pt_value_set_of_this(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC(factory)); } + +#define PT_VALUE_SET_CREATE(factory) \ + [](INTERNAL_FUNCTION_PARAMETERS) { \ + ZEND_PARSE_PARAMETERS_NONE(); \ + PT_RETURN_VAL(pt_value_set_create(PT_LC(factory))); \ + } + +#define PT_VALUE_SET_OF_ARRAYS(empty, nonEmpty) \ + [](INTERNAL_FUNCTION_PARAMETERS) { \ + ZEND_PARSE_PARAMETERS_NONE(); \ + zv::Args args_{(bool) (empty), (bool) (nonEmpty)}; \ + PT_RETURN_VAL(pt_value_set_create(PT_LC("createarrays"), 2, args_)); \ + } + +/* }}} */ + /* $position->allowed() ? new NodeClass($name) : null — the keyword and * class-name answers of Type::toNativePhpTypeNode(); allowedLc is the * lowercased NativeTypePosition check, nodeClassIdx a class-map PhpParser node diff --git a/turbo-ext/src/UnionType.cpp b/turbo-ext/src/UnionType.cpp index 6a0708b77cf..754ad5b34fe 100644 --- a/turbo-ext/src/UnionType.cpp +++ b/turbo-ext/src/UnionType.cpp @@ -2978,6 +2978,8 @@ PT_MINIT_REGISTRATION(pt_register_union_type) { reg::Class cls("PHPStan\\Type\\UnionType"); ptdecl::UnionType::declareClass(cls); + /* mirrors toValueSet() */ + cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { pt_value_set_of_members(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); }); cls.classConstantValue("EQUAL_UNION_CLASSES", pt_ut_equal_union_classes); /* the slots PT_UT_PROP_*: the six memo properties first, the promoted * $types and $normalized after them */ diff --git a/turbo-ext/src/generated/AccessoryArrayListType.h b/turbo-ext/src/generated/AccessoryArrayListType.h index 42a72abe195..3ddc059014e 100644 --- a/turbo-ext/src/generated/AccessoryArrayListType.h +++ b/turbo-ext/src/generated/AccessoryArrayListType.h @@ -164,7 +164,9 @@ inline constexpr char strings[] = "position\0" /* 1884 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1893 */ "toNativePhpTypeNode\0" /* 1925 */ - "hasTemplateOrLateResolvableType"; /* 1945 */ + "hasTemplateOrLateResolvableType\0" /* 1945 */ + "PHPStan\\Type\\ValueSet\0" /* 1977 */ + "toValueSet"; /* 1999 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -303,6 +305,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1832), /* toPhpDocNode return */ reg::packed(1884, 0, 1893), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1977), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -404,6 +407,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1813 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1999 /* toValueSet */, 0, 137, 0, 137, 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 f1898f5c466..48e512e6343 100644 --- a/turbo-ext/src/generated/AccessoryDecimalIntegerStringType.h +++ b/turbo-ext/src/generated/AccessoryDecimalIntegerStringType.h @@ -133,7 +133,9 @@ inline constexpr char strings[] = "position\0" /* 1399 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1408 */ "toNativePhpTypeNode\0" /* 1440 */ - "hasTemplateOrLateResolvableType"; /* 1460 */ + "hasTemplateOrLateResolvableType\0" /* 1460 */ + "PHPStan\\Type\\ValueSet\0" /* 1492 */ + "toValueSet"; /* 1514 */ 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 */ @@ -226,6 +228,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(26, 0, 1347), /* toPhpDocNode return */ reg::packed(1399, 0, 1408), /* toNativePhpTypeNode $position */ reg::packed(26, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(26, 0, 1492), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -296,6 +299,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1328 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1514 /* toValueSet */, 0, 91, 0, 91, 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 d5e691e4317..85be4f9fb20 100644 --- a/turbo-ext/src/generated/AccessoryLiteralStringType.h +++ b/turbo-ext/src/generated/AccessoryLiteralStringType.h @@ -124,7 +124,9 @@ inline constexpr char strings[] = "position\0" /* 1290 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1299 */ "toNativePhpTypeNode\0" /* 1331 */ - "hasTemplateOrLateResolvableType"; /* 1351 */ + "hasTemplateOrLateResolvableType\0" /* 1351 */ + "PHPStan\\Type\\ValueSet\0" /* 1383 */ + "toValueSet"; /* 1405 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -213,6 +215,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1238), /* toPhpDocNode return */ reg::packed(1290, 0, 1299), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1383), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -281,6 +284,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1219 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1405 /* toValueSet */, 0, 87, 0, 87, 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 540356b5a00..a1be91ed589 100644 --- a/turbo-ext/src/generated/AccessoryLowercaseStringType.h +++ b/turbo-ext/src/generated/AccessoryLowercaseStringType.h @@ -124,7 +124,9 @@ inline constexpr char strings[] = "position\0" /* 1290 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1299 */ "toNativePhpTypeNode\0" /* 1331 */ - "hasTemplateOrLateResolvableType"; /* 1351 */ + "hasTemplateOrLateResolvableType\0" /* 1351 */ + "PHPStan\\Type\\ValueSet\0" /* 1383 */ + "toValueSet"; /* 1405 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -213,6 +215,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1238), /* toPhpDocNode return */ reg::packed(1290, 0, 1299), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1383), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -281,6 +284,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1219 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1405 /* toValueSet */, 0, 87, 0, 87, 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 21c519e4f7b..a949cf04579 100644 --- a/turbo-ext/src/generated/AccessoryNonEmptyStringType.h +++ b/turbo-ext/src/generated/AccessoryNonEmptyStringType.h @@ -123,7 +123,9 @@ inline constexpr char strings[] = "position\0" /* 1282 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1291 */ "toNativePhpTypeNode\0" /* 1323 */ - "hasTemplateOrLateResolvableType"; /* 1343 */ + "hasTemplateOrLateResolvableType\0" /* 1343 */ + "PHPStan\\Type\\ValueSet\0" /* 1375 */ + "toValueSet"; /* 1397 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -211,6 +213,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1230), /* toPhpDocNode return */ reg::packed(1282, 0, 1291), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1375), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -278,6 +281,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1211 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1397 /* toValueSet */, 0, 86, 0, 86, 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 b53ac990724..80883a5b6cd 100644 --- a/turbo-ext/src/generated/AccessoryNonFalsyStringType.h +++ b/turbo-ext/src/generated/AccessoryNonFalsyStringType.h @@ -122,7 +122,9 @@ inline constexpr char strings[] = "position\0" /* 1259 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1268 */ "toNativePhpTypeNode\0" /* 1300 */ - "hasTemplateOrLateResolvableType"; /* 1320 */ + "hasTemplateOrLateResolvableType\0" /* 1320 */ + "PHPStan\\Type\\ValueSet\0" /* 1352 */ + "toValueSet"; /* 1374 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -208,6 +210,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1207), /* toPhpDocNode return */ reg::packed(1259, 0, 1268), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1352), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -274,6 +277,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1188 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1374 /* toValueSet */, 0, 84, 0, 84, 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 671205dbcfc..554897143ff 100644 --- a/turbo-ext/src/generated/AccessoryNumericStringType.h +++ b/turbo-ext/src/generated/AccessoryNumericStringType.h @@ -123,7 +123,9 @@ inline constexpr char strings[] = "position\0" /* 1282 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1291 */ "toNativePhpTypeNode\0" /* 1323 */ - "hasTemplateOrLateResolvableType"; /* 1343 */ + "hasTemplateOrLateResolvableType\0" /* 1343 */ + "PHPStan\\Type\\ValueSet\0" /* 1375 */ + "toValueSet"; /* 1397 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -211,6 +213,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1230), /* toPhpDocNode return */ reg::packed(1282, 0, 1291), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1375), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -278,6 +281,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1211 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1397 /* toValueSet */, 0, 86, 0, 86, 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 60f9f2ede12..5752d598cdf 100644 --- a/turbo-ext/src/generated/AccessoryUppercaseStringType.h +++ b/turbo-ext/src/generated/AccessoryUppercaseStringType.h @@ -124,7 +124,9 @@ inline constexpr char strings[] = "position\0" /* 1290 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1299 */ "toNativePhpTypeNode\0" /* 1331 */ - "hasTemplateOrLateResolvableType"; /* 1351 */ + "hasTemplateOrLateResolvableType\0" /* 1351 */ + "PHPStan\\Type\\ValueSet\0" /* 1383 */ + "toValueSet"; /* 1405 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -213,6 +215,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1238), /* toPhpDocNode return */ reg::packed(1290, 0, 1299), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1383), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -281,6 +284,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1219 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1405 /* toValueSet */, 0, 87, 0, 87, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::AccessoryUppercaseStringType diff --git a/turbo-ext/src/generated/ArrayTypeTrait.h b/turbo-ext/src/generated/ArrayTypeTrait.h index a4bca7ca647..13907eb9e25 100644 --- a/turbo-ext/src/generated/ArrayTypeTrait.h +++ b/turbo-ext/src/generated/ArrayTypeTrait.h @@ -58,7 +58,9 @@ inline constexpr char strings[] = "arithmeticPower\0" /* 664 */ "lengthType\0" /* 680 */ "preserveKeys\0" /* 691 */ - "chunkArray"; /* 704 */ + "chunkArray\0" /* 704 */ + "PHPStan\\Type\\ValueSet\0" /* 715 */ + "toValueSet"; /* 737 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 1), /* isArray return */ reg::packed(0, 0, 30), /* toArray return */ @@ -104,6 +106,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(680, 0, 30), /* chunkArray $lengthType */ reg::packed(691, 0, 1), /* chunkArray $preserveKeys */ reg::packed(0, 0, 30), /* chunkArray return */ + reg::packed(0, 0, 715), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -149,6 +152,7 @@ inline constexpr sigtab::Sig isVoid = { { 605 /* isVoid */, 0, 36, 0, 36, ZEND_A inline constexpr sigtab::Sig isScalar = { { 612 /* isScalar */, 0, 37, 0, 37, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 737 /* toValueSet */, 0, 44, 0, 44, 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 1a3fe1acbc7..56b8e54f5aa 100644 --- a/turbo-ext/src/generated/BooleanType.h +++ b/turbo-ext/src/generated/BooleanType.h @@ -80,7 +80,9 @@ inline constexpr char strings[] = "PHPStan\\Type\\NativeTypePosition\0" /* 546 */ "toNativePhpTypeNode\0" /* 578 */ "toTrinaryLogic\0" /* 598 */ - "hasTemplateOrLateResolvableType"; /* 613 */ + "hasTemplateOrLateResolvableType\0" /* 613 */ + "PHPStan\\Type\\ValueSet\0" /* 645 */ + "toValueSet"; /* 667 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getConstantStrings return */ reg::packed(12, MAY_BE_ARRAY), /* getConstantScalarTypes return */ @@ -116,6 +118,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(537, 0, 546), /* toNativePhpTypeNode $position */ reg::packed(12, 0, 263), /* toTrinaryLogic return */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 645), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -150,6 +153,7 @@ inline constexpr sigtab::Sig toPhpDocNode = { { 524 /* toPhpDocNode */, 0, 30, 0 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 667 /* toValueSet */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::BooleanType diff --git a/turbo-ext/src/generated/ClosureType.h b/turbo-ext/src/generated/ClosureType.h index b876716e9dc..eaae199cbe8 100644 --- a/turbo-ext/src/generated/ClosureType.h +++ b/turbo-ext/src/generated/ClosureType.h @@ -253,7 +253,9 @@ inline constexpr char strings[] = "position\0" /* 3253 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3262 */ "toNativePhpTypeNode\0" /* 3294 */ - "hasTemplateOrLateResolvableType"; /* 3314 */ + "hasTemplateOrLateResolvableType\0" /* 3314 */ + "PHPStan\\Type\\ValueSet\0" /* 3346 */ + "toValueSet"; /* 3368 */ 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 */ @@ -431,6 +433,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(416, 0, 3201), /* toPhpDocNode return */ reg::packed(3253, 0, 3262), /* toNativePhpTypeNode $position */ reg::packed(416, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(416, 0, 3346), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -548,6 +551,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 3186 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3368 /* toValueSet */, 0, 176, 0, 176, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ClosureType diff --git a/turbo-ext/src/generated/ConstantBooleanType.h b/turbo-ext/src/generated/ConstantBooleanType.h index 12cd9ff467a..e63295285ae 100644 --- a/turbo-ext/src/generated/ConstantBooleanType.h +++ b/turbo-ext/src/generated/ConstantBooleanType.h @@ -71,7 +71,9 @@ inline constexpr char strings[] = "PHPStan\\Type\\NativeTypePosition\0" /* 498 */ "toNativePhpTypeNode\0" /* 530 */ "type\0" /* 550 */ - "looseCompare"; /* 555 */ + "looseCompare\0" /* 555 */ + "PHPStan\\Type\\ValueSet\0" /* 568 */ + "toValueSet"; /* 590 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL), /* __construct $value */ reg::packed(18, MAY_BE_BOOL), /* getValue return */ @@ -104,6 +106,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(550, 0, 105), /* looseCompare $type */ reg::packed(71, 0, 82), /* looseCompare $phpVersion */ reg::packed(18, 0, 197), /* looseCompare return */ + reg::packed(18, 0, 568), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -132,6 +135,7 @@ inline constexpr sigtab::Sig generalize = { { 426 /* generalize */, 1, 24, 1, 25 inline constexpr sigtab::Sig toPhpDocNode = { { 476 /* toPhpDocNode */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toNativePhpTypeNode = { { 530 /* toNativePhpTypeNode */, 1, 27, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig looseCompare = { { 555 /* looseCompare */, 2, 28, 2, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 590 /* toValueSet */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ConstantBooleanType diff --git a/turbo-ext/src/generated/ConstantStringType.h b/turbo-ext/src/generated/ConstantStringType.h index 9091e7a51be..8600eebe1c5 100644 --- a/turbo-ext/src/generated/ConstantStringType.h +++ b/turbo-ext/src/generated/ConstantStringType.h @@ -113,7 +113,9 @@ inline constexpr char strings[] = "PHPStan\\Type\\ObjectType\0" /* 1153 */ "getObjectType\0" /* 1177 */ "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 1191 */ - "toPhpDocNode"; /* 1230 */ + "toPhpDocNode\0" /* 1230 */ + "PHPStan\\Type\\ValueSet\0" /* 1243 */ + "toValueSet"; /* 1265 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_STRING), /* __construct $value */ reg::packed(6, MAY_BE_BOOL, reg::NoString, false, false, 20), /* __construct $isClassString */ @@ -180,6 +182,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(38, 0, 1098), /* getConstant return */ reg::packed(38, 0, 1153), /* getObjectType return */ reg::packed(38, 0, 1191), /* toPhpDocNode return */ + reg::packed(38, 0, 1243), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -228,6 +231,7 @@ inline constexpr sigtab::Sig hasConstant = { { 1086 /* hasConstant */, 1, 59, 1, inline constexpr sigtab::Sig getConstant = { { 1141 /* getConstant */, 1, 61, 1, 62, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getObjectType = { { 1177 /* getObjectType */, 0, 63, 0, 63, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig toPhpDocNode = { { 1230 /* toPhpDocNode */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 1265 /* toValueSet */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ConstantStringType diff --git a/turbo-ext/src/generated/FloatType.h b/turbo-ext/src/generated/FloatType.h index 96bd0424125..96391c91dd7 100644 --- a/turbo-ext/src/generated/FloatType.h +++ b/turbo-ext/src/generated/FloatType.h @@ -108,7 +108,9 @@ inline constexpr char strings[] = "PHPStan\\Type\\NativeTypePosition\0" /* 1036 */ "toNativePhpTypeNode\0" /* 1068 */ "getFiniteTypes\0" /* 1088 */ - "hasTemplateOrLateResolvableType"; /* 1103 */ + "hasTemplateOrLateResolvableType\0" /* 1103 */ + "PHPStan\\Type\\ValueSet\0" /* 1135 */ + "toValueSet"; /* 1157 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -175,6 +177,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(1027, 0, 1036), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_ARRAY), /* getFiniteTypes return */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1135), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -233,6 +236,7 @@ inline constexpr sigtab::Sig toPhpDocNode = { { 1014 /* toPhpDocNode */, 0, 61, inline constexpr sigtab::Sig toNativePhpTypeNode = { { 1068 /* toNativePhpTypeNode */, 1, 62, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFiniteTypes = { { 1088 /* getFiniteTypes */, 0, 63, 0, 63, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 1103 /* hasTemplateOrLateResolvableType */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 1157 /* toValueSet */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::FloatType diff --git a/turbo-ext/src/generated/IntegerRangeType.h b/turbo-ext/src/generated/IntegerRangeType.h index dae62e6b512..1ce54ffa3aa 100644 --- a/turbo-ext/src/generated/IntegerRangeType.h +++ b/turbo-ext/src/generated/IntegerRangeType.h @@ -106,7 +106,9 @@ inline constexpr char strings[] = "getFiniteTypes\0" /* 938 */ "PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode\0" /* 953 */ "toPhpDocNode\0" /* 992 */ - "looseCompare"; /* 1005 */ + "looseCompare\0" /* 1005 */ + "PHPStan\\Type\\ValueSet\0" /* 1018 */ + "toValueSet"; /* 1040 */ 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 */ @@ -191,6 +193,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(322, 0, 29), /* looseCompare $type */ reg::packed(581, 0, 592), /* looseCompare $phpVersion */ reg::packed(28, 0, 815), /* looseCompare return */ + reg::packed(28, 0, 1018), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -236,6 +239,7 @@ inline constexpr sigtab::Sig tryRemove = { { 928 /* tryRemove */, 1, 76, 1, 77, inline constexpr sigtab::Sig getFiniteTypes = { { 938 /* getFiniteTypes */, 0, 78, 0, 78, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toPhpDocNode = { { 992 /* toPhpDocNode */, 0, 79, 0, 79, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig looseCompare = { { 1005 /* looseCompare */, 2, 80, 2, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 1040 /* toValueSet */, 0, 83, 0, 83, 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 3cc3dc3a679..303d818f09c 100644 --- a/turbo-ext/src/generated/IntegerType.h +++ b/turbo-ext/src/generated/IntegerType.h @@ -81,7 +81,9 @@ inline constexpr char strings[] = "position\0" /* 543 */ "PHPStan\\Type\\NativeTypePosition\0" /* 552 */ "toNativePhpTypeNode\0" /* 584 */ - "hasTemplateOrLateResolvableType"; /* 604 */ + "hasTemplateOrLateResolvableType\0" /* 604 */ + "PHPStan\\Type\\ValueSet\0" /* 636 */ + "toValueSet"; /* 658 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ @@ -117,6 +119,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(46, 0, 491), /* toPhpDocNode return */ reg::packed(543, 0, 552), /* toNativePhpTypeNode $position */ reg::packed(46, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(46, 0, 636), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -151,6 +154,7 @@ inline constexpr sigtab::Sig arithmeticPower = { { 475 /* arithmeticPower */, 2, inline constexpr sigtab::Sig toPhpDocNode = { { 530 /* toPhpDocNode */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toNativePhpTypeNode = { { 584 /* toNativePhpTypeNode */, 1, 32, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 604 /* hasTemplateOrLateResolvableType */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 658 /* toValueSet */, 0, 34, 0, 34, 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 8b7eea42d6b..d534873c26d 100644 --- a/turbo-ext/src/generated/IntersectionType.h +++ b/turbo-ext/src/generated/IntersectionType.h @@ -286,7 +286,9 @@ inline constexpr char strings[] = "position\0" /* 3712 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3721 */ "toNativePhpTypeNode\0" /* 3753 */ - "hasTemplateOrLateResolvableType"; /* 3773 */ + "hasTemplateOrLateResolvableType\0" /* 3773 */ + "PHPStan\\Type\\ValueSet\0" /* 3805 */ + "toValueSet"; /* 3827 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $types */ reg::packed(18, MAY_BE_ARRAY), /* getTypes return */ @@ -542,6 +544,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(18, 0, 3660), /* toPhpDocNode return */ reg::packed(3712, 0, 3721), /* toNativePhpTypeNode $position */ reg::packed(18, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(18, 0, 3805), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -698,6 +701,7 @@ inline constexpr sigtab::Sig intersectTypesPreserveTemplateType = { { 3625 /* in 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3827 /* toValueSet */, 0, 254, 0, 254, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IntersectionType diff --git a/turbo-ext/src/generated/LateResolvableTypeTrait.h b/turbo-ext/src/generated/LateResolvableTypeTrait.h index e11ba5a53b6..0b380b31127 100644 --- a/turbo-ext/src/generated/LateResolvableTypeTrait.h +++ b/turbo-ext/src/generated/LateResolvableTypeTrait.h @@ -193,7 +193,9 @@ inline constexpr char strings[] = "position\0" /* 3101 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3110 */ "toNativePhpTypeNode\0" /* 3142 */ - "hasTemplateOrLateResolvableType"; /* 3162 */ + "hasTemplateOrLateResolvableType\0" /* 3162 */ + "PHPStan\\Type\\ValueSet\0" /* 3194 */ + "toValueSet"; /* 3216 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* getObjectClassNames return */ reg::packed(0, MAY_BE_ARRAY), /* getObjectClassReflections return */ @@ -408,6 +410,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 99), /* getResult return */ reg::packed(3101, 0, 3110), /* toNativePhpTypeNode $position */ reg::packed(0, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(0, 0, 3194), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -545,6 +548,7 @@ inline constexpr sigtab::Sig resolve = { { 3083 /* resolve */, 0, 209, 0, 209, Z 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3216 /* toValueSet */, 0, 213, 0, 213, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::LateResolvableTypeTrait diff --git a/turbo-ext/src/generated/NeverType.h b/turbo-ext/src/generated/NeverType.h index 7d836fea0a0..09d71a772ae 100644 --- a/turbo-ext/src/generated/NeverType.h +++ b/turbo-ext/src/generated/NeverType.h @@ -221,7 +221,9 @@ inline constexpr char strings[] = "position\0" /* 3023 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3032 */ "toNativePhpTypeNode\0" /* 3064 */ - "hasTemplateOrLateResolvableType"; /* 3084 */ + "hasTemplateOrLateResolvableType\0" /* 3084 */ + "PHPStan\\Type\\ValueSet\0" /* 3116 */ + "toValueSet"; /* 3138 */ 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 */ @@ -422,6 +424,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(41, 0, 2971), /* toPhpDocNode return */ reg::packed(3023, 0, 3032), /* toNativePhpTypeNode $position */ reg::packed(41, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(41, 0, 3116), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -554,6 +557,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 2956 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3138 /* toValueSet */, 0, 199, 0, 199, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NeverType diff --git a/turbo-ext/src/generated/NonEmptyArrayType.h b/turbo-ext/src/generated/NonEmptyArrayType.h index c1386a547b4..5a17763b05e 100644 --- a/turbo-ext/src/generated/NonEmptyArrayType.h +++ b/turbo-ext/src/generated/NonEmptyArrayType.h @@ -164,7 +164,9 @@ inline constexpr char strings[] = "position\0" /* 1884 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1893 */ "toNativePhpTypeNode\0" /* 1925 */ - "hasTemplateOrLateResolvableType"; /* 1945 */ + "hasTemplateOrLateResolvableType\0" /* 1945 */ + "PHPStan\\Type\\ValueSet\0" /* 1977 */ + "toValueSet"; /* 1999 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -303,6 +305,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1832), /* toPhpDocNode return */ reg::packed(1884, 0, 1893), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1977), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -404,6 +407,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1813 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1999 /* toValueSet */, 0, 137, 0, 137, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NonEmptyArrayType diff --git a/turbo-ext/src/generated/NullType.h b/turbo-ext/src/generated/NullType.h index cd636fa80ec..7be3c8c739e 100644 --- a/turbo-ext/src/generated/NullType.h +++ b/turbo-ext/src/generated/NullType.h @@ -126,7 +126,9 @@ inline constexpr char strings[] = "position\0" /* 1396 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1405 */ "toNativePhpTypeNode\0" /* 1437 */ - "hasTemplateOrLateResolvableType"; /* 1457 */ + "hasTemplateOrLateResolvableType\0" /* 1457 */ + "PHPStan\\Type\\ValueSet\0" /* 1489 */ + "toValueSet"; /* 1511 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -222,6 +224,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1344), /* toPhpDocNode return */ reg::packed(1396, 0, 1405), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1489), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -293,6 +296,7 @@ inline constexpr sigtab::Sig arithmeticPower = { { 1328 /* arithmeticPower */, 2 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1511 /* toValueSet */, 0, 94, 0, 94, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NullType diff --git a/turbo-ext/src/generated/NumberSet.h b/turbo-ext/src/generated/NumberSet.h index 56b4d755c5d..e4631834994 100644 --- a/turbo-ext/src/generated/NumberSet.h +++ b/turbo-ext/src/generated/NumberSet.h @@ -39,43 +39,46 @@ inline constexpr char strings[] = "getIntegerIntervals\0" /* 49 */ "getFloatIntervals\0" /* 69 */ "hasNan\0" /* 87 */ - "right\0" /* 94 */ + "other\0" /* 94 */ "PHPStan\\Type\\NumberSet\0" /* 100 */ - "phpVersion\0" /* 123 */ - "PHPStan\\Php\\PhpVersion\0" /* 134 */ - "PHPStan\\Type\\Type\0" /* 157 */ - "add\0" /* 175 */ - "subtract\0" /* 179 */ - "multiply\0" /* 188 */ - "divide\0" /* 197 */ - "shiftLeft\0" /* 204 */ - "shiftRight\0" /* 214 */ - "operator\0" /* 225 */ - "calculate\0" /* 234 */ - "hasAnyInteger\0" /* 244 */ - "countMembers\0" /* 258 */ - "isEmpty\0" /* 271 */ - "hasFloatRange\0" /* 279 */ - "toHull\0" /* 293 */ - "interval\0" /* 300 */ - "integerToFloatInterval\0" /* 309 */ - "splitIntegerDivisor\0" /* 332 */ - "left\0" /* 352 */ - "calculateIntegers\0" /* 357 */ - "value\0" /* 375 */ - "shift\0" /* 381 */ - "shiftLeftOverflows\0" /* 387 */ - "divideIntegers\0" /* 406 */ - "numerator\0" /* 421 */ - "divisor\0" /* 431 */ - "divideBound\0" /* 439 */ - "toIntBound\0" /* 451 */ - "calculateFloats\0" /* 462 */ - "calculateFloat\0" /* 478 */ - "bound\0" /* 493 */ - "min\0" /* 499 */ - "max\0" /* 503 */ - "getFloatBoundWithInnerNeighbour"; /* 507 */ + "union\0" /* 123 */ + "intersect\0" /* 129 */ + "right\0" /* 139 */ + "phpVersion\0" /* 145 */ + "PHPStan\\Php\\PhpVersion\0" /* 156 */ + "PHPStan\\Type\\Type\0" /* 179 */ + "add\0" /* 197 */ + "subtract\0" /* 201 */ + "multiply\0" /* 210 */ + "divide\0" /* 219 */ + "shiftLeft\0" /* 226 */ + "shiftRight\0" /* 236 */ + "operator\0" /* 247 */ + "calculate\0" /* 256 */ + "hasAnyInteger\0" /* 266 */ + "countMembers\0" /* 280 */ + "isEmpty\0" /* 293 */ + "hasFloatRange\0" /* 301 */ + "toHull\0" /* 315 */ + "interval\0" /* 322 */ + "integerToFloatInterval\0" /* 331 */ + "splitIntegerDivisor\0" /* 354 */ + "left\0" /* 374 */ + "calculateIntegers\0" /* 379 */ + "value\0" /* 397 */ + "shift\0" /* 403 */ + "shiftLeftOverflows\0" /* 409 */ + "divideIntegers\0" /* 428 */ + "numerator\0" /* 443 */ + "divisor\0" /* 453 */ + "divideBound\0" /* 461 */ + "toIntBound\0" /* 473 */ + "calculateFloats\0" /* 484 */ + "calculateFloat\0" /* 500 */ + "bound\0" /* 515 */ + "min\0" /* 521 */ + "max\0" /* 525 */ + "getFloatBoundWithInnerNeighbour"; /* 529 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $integerIntervals */ reg::packed(17, MAY_BE_ARRAY), /* __construct $floatIntervals */ @@ -83,65 +86,69 @@ inline constexpr reg::PackedArg args[] = { reg::packed(48, MAY_BE_ARRAY), /* getIntegerIntervals return */ reg::packed(48, MAY_BE_ARRAY), /* getFloatIntervals return */ reg::packed(48, MAY_BE_BOOL), /* hasNan return */ - reg::packed(94, 0, 100), /* add $right */ - reg::packed(123, 0, 134), /* add $phpVersion */ - reg::packed(48, 0, 157), /* add return */ - reg::packed(94, 0, 100), /* subtract $right */ - reg::packed(123, 0, 134), /* subtract $phpVersion */ - reg::packed(48, 0, 157), /* subtract return */ - reg::packed(94, 0, 100), /* multiply $right */ - reg::packed(123, 0, 134), /* multiply $phpVersion */ - reg::packed(48, 0, 157), /* multiply return */ - reg::packed(94, 0, 100), /* divide $right */ - reg::packed(123, 0, 134), /* divide $phpVersion */ - reg::packed(48, 0, 157), /* divide return */ - reg::packed(94, 0, 100), /* shiftLeft $right */ - reg::packed(123, 0, 134), /* shiftLeft $phpVersion */ - reg::packed(48, 0, 157), /* shiftLeft return */ - reg::packed(94, 0, 100), /* shiftRight $right */ - reg::packed(123, 0, 134), /* shiftRight $phpVersion */ - reg::packed(48, 0, 157), /* shiftRight return */ - reg::packed(225, MAY_BE_STRING), /* calculate $operator */ - reg::packed(94, 0, 100), /* calculate $right */ - reg::packed(123, 0, 134), /* calculate $phpVersion */ - reg::packed(48, 0, 157), /* calculate return */ + reg::packed(94, 0, 100), /* union $other */ + reg::packed(48, 0, 100), /* union return */ + reg::packed(94, 0, 100), /* intersect $other */ + reg::packed(48, 0, 100), /* intersect return */ + reg::packed(139, 0, 100), /* add $right */ + reg::packed(145, 0, 156), /* add $phpVersion */ + reg::packed(48, 0, 179), /* add return */ + reg::packed(139, 0, 100), /* subtract $right */ + reg::packed(145, 0, 156), /* subtract $phpVersion */ + reg::packed(48, 0, 179), /* subtract return */ + reg::packed(139, 0, 100), /* multiply $right */ + reg::packed(145, 0, 156), /* multiply $phpVersion */ + reg::packed(48, 0, 179), /* multiply return */ + reg::packed(139, 0, 100), /* divide $right */ + reg::packed(145, 0, 156), /* divide $phpVersion */ + reg::packed(48, 0, 179), /* divide return */ + reg::packed(139, 0, 100), /* shiftLeft $right */ + reg::packed(145, 0, 156), /* shiftLeft $phpVersion */ + reg::packed(48, 0, 179), /* shiftLeft return */ + reg::packed(139, 0, 100), /* shiftRight $right */ + reg::packed(145, 0, 156), /* shiftRight $phpVersion */ + reg::packed(48, 0, 179), /* shiftRight return */ + reg::packed(247, MAY_BE_STRING), /* calculate $operator */ + reg::packed(139, 0, 100), /* calculate $right */ + reg::packed(145, 0, 156), /* calculate $phpVersion */ + reg::packed(48, 0, 179), /* calculate return */ reg::packed(48, MAY_BE_BOOL), /* hasAnyInteger return */ reg::packed(48, MAY_BE_LONG), /* countMembers return */ reg::packed(48, MAY_BE_BOOL), /* isEmpty return */ reg::packed(48, MAY_BE_BOOL), /* hasFloatRange return */ reg::packed(48, 0, 100), /* toHull return */ - reg::packed(300, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ + reg::packed(322, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ reg::packed(48, MAY_BE_ARRAY), /* integerToFloatInterval return */ - reg::packed(300, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ + reg::packed(322, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ reg::packed(48, MAY_BE_ARRAY), /* splitIntegerDivisor return */ - reg::packed(225, MAY_BE_STRING), /* calculateIntegers $operator */ - reg::packed(352, MAY_BE_ARRAY), /* calculateIntegers $left */ - reg::packed(94, MAY_BE_ARRAY), /* calculateIntegers $right */ - reg::packed(48, 0, 157), /* calculateIntegers return */ - reg::packed(375, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(381, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(247, MAY_BE_STRING), /* calculateIntegers $operator */ + reg::packed(374, MAY_BE_ARRAY), /* calculateIntegers $left */ + reg::packed(139, MAY_BE_ARRAY), /* calculateIntegers $right */ + reg::packed(48, 0, 179), /* calculateIntegers return */ + reg::packed(397, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(403, MAY_BE_LONG), /* shiftLeftOverflows $shift */ reg::packed(48, MAY_BE_BOOL), /* shiftLeftOverflows return */ - reg::packed(352, MAY_BE_ARRAY), /* divideIntegers $left */ - reg::packed(94, MAY_BE_ARRAY), /* divideIntegers $right */ - reg::packed(123, 0, 134), /* divideIntegers $phpVersion */ + reg::packed(374, MAY_BE_ARRAY), /* divideIntegers $left */ + reg::packed(139, MAY_BE_ARRAY), /* divideIntegers $right */ + reg::packed(145, 0, 156), /* divideIntegers $phpVersion */ reg::packed(48, MAY_BE_ARRAY), /* divideIntegers return */ - reg::packed(421, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ - reg::packed(431, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ + reg::packed(443, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ + reg::packed(453, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ reg::packed(48, MAY_BE_ARRAY), /* divideBound return */ - reg::packed(375, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(397, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ - reg::packed(225, MAY_BE_STRING), /* calculateFloats $operator */ - reg::packed(352, MAY_BE_ARRAY), /* calculateFloats $left */ - reg::packed(94, MAY_BE_ARRAY), /* calculateFloats $right */ - reg::packed(123, 0, 134), /* calculateFloats $phpVersion */ - reg::packed(48, 0, 157), /* calculateFloats return */ - reg::packed(225, MAY_BE_STRING), /* calculateFloat $operator */ - reg::packed(352, MAY_BE_DOUBLE), /* calculateFloat $left */ - reg::packed(94, MAY_BE_DOUBLE), /* calculateFloat $right */ + reg::packed(247, MAY_BE_STRING), /* calculateFloats $operator */ + reg::packed(374, MAY_BE_ARRAY), /* calculateFloats $left */ + reg::packed(139, MAY_BE_ARRAY), /* calculateFloats $right */ + reg::packed(145, 0, 156), /* calculateFloats $phpVersion */ + reg::packed(48, 0, 179), /* calculateFloats return */ + reg::packed(247, MAY_BE_STRING), /* calculateFloat $operator */ + reg::packed(374, MAY_BE_DOUBLE), /* calculateFloat $left */ + reg::packed(139, MAY_BE_DOUBLE), /* calculateFloat $right */ reg::packed(48, MAY_BE_DOUBLE), /* calculateFloat return */ - reg::packed(493, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ - reg::packed(499, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ - reg::packed(503, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ + reg::packed(515, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ + reg::packed(521, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ + reg::packed(525, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ reg::packed(48, MAY_BE_ARRAY), /* getFloatBoundWithInnerNeighbour return */ }; using Sig = reg::Sig; @@ -153,28 +160,30 @@ inline constexpr sigtab::Sig __construct = { { 36 /* __construct */, 3, 0, 3, re inline constexpr sigtab::Sig getIntegerIntervals = { { 49 /* getIntegerIntervals */, 0, 3, 0, 3, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getFloatIntervals = { { 69 /* getFloatIntervals */, 0, 4, 0, 4, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig hasNan = { { 87 /* hasNan */, 0, 5, 0, 5, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig add = { { 175 /* add */, 2, 6, 2, 8, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig subtract = { { 179 /* subtract */, 2, 9, 2, 11, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig multiply = { { 188 /* multiply */, 2, 12, 2, 14, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig divide = { { 197 /* divide */, 2, 15, 2, 17, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 204 /* shiftLeft */, 2, 18, 2, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shiftRight = { { 214 /* shiftRight */, 2, 21, 2, 23, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig calculate = { { 234 /* calculate */, 3, 24, 3, 27, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasAnyInteger = { { 244 /* hasAnyInteger */, 0, 28, 0, 28, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig countMembers = { { 258 /* countMembers */, 0, 29, 0, 29, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isEmpty = { { 271 /* isEmpty */, 0, 30, 0, 30, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasFloatRange = { { 279 /* hasFloatRange */, 0, 31, 0, 31, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toHull = { { 293 /* toHull */, 0, 32, 0, 32, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig integerToFloatInterval = { { 309 /* integerToFloatInterval */, 1, 33, 1, 34, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig splitIntegerDivisor = { { 332 /* splitIntegerDivisor */, 1, 35, 1, 36, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateIntegers = { { 357 /* calculateIntegers */, 3, 37, 3, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 387 /* shiftLeftOverflows */, 2, 41, 2, 43, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntegers = { { 406 /* divideIntegers */, 3, 44, 3, 47, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBound = { { 439 /* divideBound */, 2, 48, 2, 50, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntBound = { { 451 /* toIntBound */, 1, 51, 1, 52, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloats = { { 462 /* calculateFloats */, 4, 53, 4, 57, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloat = { { 478 /* calculateFloat */, 3, 58, 3, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 507 /* getFloatBoundWithInnerNeighbour */, 3, 62, 3, 65, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig union_ = { { 123 /* union */, 1, 6, 1, 7, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig intersect = { { 129 /* intersect */, 1, 8, 1, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig add = { { 197 /* add */, 2, 10, 2, 12, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig subtract = { { 201 /* subtract */, 2, 13, 2, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig multiply = { { 210 /* multiply */, 2, 16, 2, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig divide = { { 219 /* divide */, 2, 19, 2, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 226 /* shiftLeft */, 2, 22, 2, 24, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftRight = { { 236 /* shiftRight */, 2, 25, 2, 27, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculate = { { 256 /* calculate */, 3, 28, 3, 31, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasAnyInteger = { { 266 /* hasAnyInteger */, 0, 32, 0, 32, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig countMembers = { { 280 /* countMembers */, 0, 33, 0, 33, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isEmpty = { { 293 /* isEmpty */, 0, 34, 0, 34, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasFloatRange = { { 301 /* hasFloatRange */, 0, 35, 0, 35, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toHull = { { 315 /* toHull */, 0, 36, 0, 36, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig integerToFloatInterval = { { 331 /* integerToFloatInterval */, 1, 37, 1, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig splitIntegerDivisor = { { 354 /* splitIntegerDivisor */, 1, 39, 1, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateIntegers = { { 379 /* calculateIntegers */, 3, 41, 3, 44, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 409 /* shiftLeftOverflows */, 2, 45, 2, 47, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntegers = { { 428 /* divideIntegers */, 3, 48, 3, 51, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBound = { { 461 /* divideBound */, 2, 52, 2, 54, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 473 /* toIntBound */, 1, 55, 1, 56, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloats = { { 484 /* calculateFloats */, 4, 57, 4, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloat = { { 500 /* calculateFloat */, 3, 62, 3, 65, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 529 /* getFloatBoundWithInnerNeighbour */, 3, 66, 3, 69, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::NumberSet diff --git a/turbo-ext/src/generated/ObjectShapeType.h b/turbo-ext/src/generated/ObjectShapeType.h index a1e2eb57c6b..edc86fa57b7 100644 --- a/turbo-ext/src/generated/ObjectShapeType.h +++ b/turbo-ext/src/generated/ObjectShapeType.h @@ -101,7 +101,9 @@ inline constexpr char strings[] = "position\0" /* 1195 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1204 */ "toNativePhpTypeNode\0" /* 1236 */ - "hasTemplateOrLateResolvableType"; /* 1256 */ + "hasTemplateOrLateResolvableType\0" /* 1256 */ + "PHPStan\\Type\\ValueSet\0" /* 1288 */ + "toValueSet"; /* 1310 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $properties */ reg::packed(11, MAY_BE_ARRAY), /* __construct $optionalProperties */ @@ -166,6 +168,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(42, 0, 1143), /* toPhpDocNode return */ reg::packed(1195, 0, 1204), /* toNativePhpTypeNode $position */ reg::packed(42, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(42, 0, 1288), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -205,6 +208,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 1128 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1310 /* toValueSet */, 0, 63, 0, 63, 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 be2ec69571d..7e0a8c6e061 100644 --- a/turbo-ext/src/generated/ObjectType.h +++ b/turbo-ext/src/generated/ObjectType.h @@ -234,7 +234,9 @@ inline constexpr char strings[] = "position\0" /* 3057 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3066 */ "toNativePhpTypeNode\0" /* 3098 */ - "hasTemplateOrLateResolvableType"; /* 3118 */ + "hasTemplateOrLateResolvableType\0" /* 3118 */ + "PHPStan\\Type\\ValueSet\0" /* 3150 */ + "toValueSet"; /* 3172 */ 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 */ @@ -414,6 +416,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(111, 0, 3005), /* toPhpDocNode return */ reg::packed(3057, 0, 3066), /* toNativePhpTypeNode $position */ reg::packed(111, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(111, 0, 3150), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -538,6 +541,7 @@ inline constexpr sigtab::Sig arithmeticPower = { { 2989 /* arithmeticPower */, 2 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3172 /* toValueSet */, 0, 178, 0, 178, 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 9f9d61804ab..3939f1a89a2 100644 --- a/turbo-ext/src/generated/ObjectWithoutClassType.h +++ b/turbo-ext/src/generated/ObjectWithoutClassType.h @@ -80,7 +80,9 @@ inline constexpr char strings[] = "position\0" /* 621 */ "PHPStan\\Type\\NativeTypePosition\0" /* 630 */ "toNativePhpTypeNode\0" /* 662 */ - "hasTemplateOrLateResolvableType"; /* 682 */ + "hasTemplateOrLateResolvableType\0" /* 682 */ + "PHPStan\\Type\\ValueSet\0" /* 714 */ + "toValueSet"; /* 736 */ 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 */ @@ -118,6 +120,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(50, 0, 569), /* toPhpDocNode return */ reg::packed(621, 0, 630), /* toNativePhpTypeNode $position */ reg::packed(50, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(50, 0, 714), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -147,6 +150,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 554 /* getFiniteTypes */, 0, 3 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 736 /* toValueSet */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ObjectWithoutClassType diff --git a/turbo-ext/src/generated/OperatorTypeTrait.h b/turbo-ext/src/generated/OperatorTypeTrait.h index bbdf424b01c..45b4eee7b8c 100644 --- a/turbo-ext/src/generated/OperatorTypeTrait.h +++ b/turbo-ext/src/generated/OperatorTypeTrait.h @@ -17,73 +17,76 @@ inline constexpr char strings[] = "exponentiate\0" /* 28 */ "PHPStan\\Type\\NumberSet\0" /* 41 */ "toNumberSet\0" /* 64 */ - "right\0" /* 76 */ - "phpVersion\0" /* 82 */ - "PHPStan\\Php\\PhpVersion\0" /* 93 */ - "arithmeticAdd\0" /* 116 */ - "arithmeticSubtract\0" /* 130 */ - "arithmeticMultiply\0" /* 149 */ - "arithmeticDivide\0" /* 168 */ - "arithmeticIntDivide\0" /* 185 */ - "arithmeticModulo\0" /* 205 */ - "arithmeticNegate\0" /* 222 */ - "bitwiseAnd\0" /* 239 */ - "bitwiseOr\0" /* 250 */ - "bitwiseXor\0" /* 260 */ - "bitwiseShiftLeft\0" /* 271 */ - "bitwiseShiftRight\0" /* 288 */ - "getPossiblyLooselyEqualType\0" /* 306 */ - "getAlwaysLooselyEqualType\0" /* 334 */ - "stringConcatenate\0" /* 360 */ - "arrayUnion"; /* 378 */ + "PHPStan\\Type\\ValueSet\0" /* 76 */ + "toValueSet\0" /* 98 */ + "right\0" /* 109 */ + "phpVersion\0" /* 115 */ + "PHPStan\\Php\\PhpVersion\0" /* 126 */ + "arithmeticAdd\0" /* 149 */ + "arithmeticSubtract\0" /* 163 */ + "arithmeticMultiply\0" /* 182 */ + "arithmeticDivide\0" /* 201 */ + "arithmeticIntDivide\0" /* 218 */ + "arithmeticModulo\0" /* 238 */ + "arithmeticNegate\0" /* 255 */ + "bitwiseAnd\0" /* 272 */ + "bitwiseOr\0" /* 283 */ + "bitwiseXor\0" /* 293 */ + "bitwiseShiftLeft\0" /* 304 */ + "bitwiseShiftRight\0" /* 321 */ + "getPossiblyLooselyEqualType\0" /* 339 */ + "getAlwaysLooselyEqualType\0" /* 367 */ + "stringConcatenate\0" /* 393 */ + "arrayUnion"; /* 411 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* exponentiate $exponent */ reg::packed(27, 0, 9), /* exponentiate return */ reg::packed(27, MAY_BE_NULL, 41), /* toNumberSet return */ - reg::packed(76, 0, 9), /* arithmeticAdd $right */ - reg::packed(82, 0, 93), /* arithmeticAdd $phpVersion */ + reg::packed(27, 0, 76), /* toValueSet return */ + reg::packed(109, 0, 9), /* arithmeticAdd $right */ + reg::packed(115, 0, 126), /* arithmeticAdd $phpVersion */ reg::packed(27, 0, 9), /* arithmeticAdd return */ - reg::packed(76, 0, 9), /* arithmeticSubtract $right */ - reg::packed(82, 0, 93), /* arithmeticSubtract $phpVersion */ + reg::packed(109, 0, 9), /* arithmeticSubtract $right */ + reg::packed(115, 0, 126), /* arithmeticSubtract $phpVersion */ reg::packed(27, 0, 9), /* arithmeticSubtract return */ - reg::packed(76, 0, 9), /* arithmeticMultiply $right */ - reg::packed(82, 0, 93), /* arithmeticMultiply $phpVersion */ + reg::packed(109, 0, 9), /* arithmeticMultiply $right */ + reg::packed(115, 0, 126), /* arithmeticMultiply $phpVersion */ reg::packed(27, 0, 9), /* arithmeticMultiply return */ - reg::packed(76, 0, 9), /* arithmeticDivide $right */ - reg::packed(82, 0, 93), /* arithmeticDivide $phpVersion */ + reg::packed(109, 0, 9), /* arithmeticDivide $right */ + reg::packed(115, 0, 126), /* arithmeticDivide $phpVersion */ reg::packed(27, 0, 9), /* arithmeticDivide return */ - reg::packed(76, 0, 9), /* arithmeticIntDivide $right */ - reg::packed(82, 0, 93), /* arithmeticIntDivide $phpVersion */ + reg::packed(109, 0, 9), /* arithmeticIntDivide $right */ + reg::packed(115, 0, 126), /* arithmeticIntDivide $phpVersion */ reg::packed(27, 0, 9), /* arithmeticIntDivide return */ - reg::packed(76, 0, 9), /* arithmeticModulo $right */ - reg::packed(82, 0, 93), /* arithmeticModulo $phpVersion */ + reg::packed(109, 0, 9), /* arithmeticModulo $right */ + reg::packed(115, 0, 126), /* arithmeticModulo $phpVersion */ reg::packed(27, 0, 9), /* arithmeticModulo return */ - reg::packed(82, 0, 93), /* arithmeticNegate $phpVersion */ + reg::packed(115, 0, 126), /* arithmeticNegate $phpVersion */ reg::packed(27, 0, 9), /* arithmeticNegate return */ - reg::packed(76, 0, 9), /* bitwiseAnd $right */ - reg::packed(82, 0, 93), /* bitwiseAnd $phpVersion */ + reg::packed(109, 0, 9), /* bitwiseAnd $right */ + reg::packed(115, 0, 126), /* bitwiseAnd $phpVersion */ reg::packed(27, 0, 9), /* bitwiseAnd return */ - reg::packed(76, 0, 9), /* bitwiseOr $right */ - reg::packed(82, 0, 93), /* bitwiseOr $phpVersion */ + reg::packed(109, 0, 9), /* bitwiseOr $right */ + reg::packed(115, 0, 126), /* bitwiseOr $phpVersion */ reg::packed(27, 0, 9), /* bitwiseOr return */ - reg::packed(76, 0, 9), /* bitwiseXor $right */ - reg::packed(82, 0, 93), /* bitwiseXor $phpVersion */ + reg::packed(109, 0, 9), /* bitwiseXor $right */ + reg::packed(115, 0, 126), /* bitwiseXor $phpVersion */ reg::packed(27, 0, 9), /* bitwiseXor return */ - reg::packed(76, 0, 9), /* bitwiseShiftLeft $right */ - reg::packed(82, 0, 93), /* bitwiseShiftLeft $phpVersion */ + reg::packed(109, 0, 9), /* bitwiseShiftLeft $right */ + reg::packed(115, 0, 126), /* bitwiseShiftLeft $phpVersion */ reg::packed(27, 0, 9), /* bitwiseShiftLeft return */ - reg::packed(76, 0, 9), /* bitwiseShiftRight $right */ - reg::packed(82, 0, 93), /* bitwiseShiftRight $phpVersion */ + reg::packed(109, 0, 9), /* bitwiseShiftRight $right */ + reg::packed(115, 0, 126), /* bitwiseShiftRight $phpVersion */ reg::packed(27, 0, 9), /* bitwiseShiftRight return */ - reg::packed(82, 0, 93), /* getPossiblyLooselyEqualType $phpVersion */ + reg::packed(115, 0, 126), /* getPossiblyLooselyEqualType $phpVersion */ reg::packed(27, 0, 9), /* getPossiblyLooselyEqualType return */ - reg::packed(82, 0, 93), /* getAlwaysLooselyEqualType $phpVersion */ + reg::packed(115, 0, 126), /* getAlwaysLooselyEqualType $phpVersion */ reg::packed(27, 0, 9), /* getAlwaysLooselyEqualType return */ - reg::packed(76, 0, 9), /* stringConcatenate $right */ - reg::packed(82, 0, 93), /* stringConcatenate $phpVersion */ + reg::packed(109, 0, 9), /* stringConcatenate $right */ + reg::packed(115, 0, 126), /* stringConcatenate $phpVersion */ reg::packed(27, 0, 9), /* stringConcatenate return */ - reg::packed(76, 0, 9), /* arrayUnion $right */ - reg::packed(82, 0, 93), /* arrayUnion $phpVersion */ + reg::packed(109, 0, 9), /* arrayUnion $right */ + reg::packed(115, 0, 126), /* arrayUnion $phpVersion */ reg::packed(27, 0, 9), /* arrayUnion return */ }; using Sig = reg::Sig; @@ -93,22 +96,23 @@ using Sig = reg::Sig; namespace sig { inline constexpr sigtab::Sig exponentiate = { { 28 /* exponentiate */, 1, 0, 1, 1, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toNumberSet = { { 64 /* toNumberSet */, 0, 2, 0, 2, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticAdd = { { 116 /* arithmeticAdd */, 2, 3, 2, 5, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticSubtract = { { 130 /* arithmeticSubtract */, 2, 6, 2, 8, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticMultiply = { { 149 /* arithmeticMultiply */, 2, 9, 2, 11, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticDivide = { { 168 /* arithmeticDivide */, 2, 12, 2, 14, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticIntDivide = { { 185 /* arithmeticIntDivide */, 2, 15, 2, 17, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticModulo = { { 205 /* arithmeticModulo */, 2, 18, 2, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticNegate = { { 222 /* arithmeticNegate */, 1, 21, 1, 22, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseAnd = { { 239 /* bitwiseAnd */, 2, 23, 2, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseOr = { { 250 /* bitwiseOr */, 2, 26, 2, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseXor = { { 260 /* bitwiseXor */, 2, 29, 2, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftLeft = { { 271 /* bitwiseShiftLeft */, 2, 32, 2, 34, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftRight = { { 288 /* bitwiseShiftRight */, 2, 35, 2, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 306 /* getPossiblyLooselyEqualType */, 1, 38, 1, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 334 /* getAlwaysLooselyEqualType */, 1, 40, 1, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig stringConcatenate = { { 360 /* stringConcatenate */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 378 /* arrayUnion */, 2, 45, 2, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 98 /* toValueSet */, 0, 3, 0, 3, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticAdd = { { 149 /* arithmeticAdd */, 2, 4, 2, 6, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticSubtract = { { 163 /* arithmeticSubtract */, 2, 7, 2, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticMultiply = { { 182 /* arithmeticMultiply */, 2, 10, 2, 12, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticDivide = { { 201 /* arithmeticDivide */, 2, 13, 2, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticIntDivide = { { 218 /* arithmeticIntDivide */, 2, 16, 2, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticModulo = { { 238 /* arithmeticModulo */, 2, 19, 2, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticNegate = { { 255 /* arithmeticNegate */, 1, 22, 1, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 272 /* bitwiseAnd */, 2, 24, 2, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 283 /* bitwiseOr */, 2, 27, 2, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 293 /* bitwiseXor */, 2, 30, 2, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftLeft = { { 304 /* bitwiseShiftLeft */, 2, 33, 2, 35, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftRight = { { 321 /* bitwiseShiftRight */, 2, 36, 2, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 339 /* getPossiblyLooselyEqualType */, 1, 39, 1, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 367 /* getAlwaysLooselyEqualType */, 1, 41, 1, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig stringConcatenate = { { 393 /* stringConcatenate */, 2, 43, 2, 45, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 411 /* arrayUnion */, 2, 46, 2, 48, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeTrait diff --git a/turbo-ext/src/generated/OversizedArrayType.h b/turbo-ext/src/generated/OversizedArrayType.h index 9007b4eb3a4..9e6512e07d0 100644 --- a/turbo-ext/src/generated/OversizedArrayType.h +++ b/turbo-ext/src/generated/OversizedArrayType.h @@ -164,7 +164,9 @@ inline constexpr char strings[] = "position\0" /* 1894 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1903 */ "toNativePhpTypeNode\0" /* 1935 */ - "hasTemplateOrLateResolvableType"; /* 1955 */ + "hasTemplateOrLateResolvableType\0" /* 1955 */ + "PHPStan\\Type\\ValueSet\0" /* 1987 */ + "toValueSet"; /* 2009 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -304,6 +306,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 1842), /* toPhpDocNode return */ reg::packed(1894, 0, 1903), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1987), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -406,6 +409,7 @@ inline constexpr sigtab::Sig getDefaultBaseType = { { 1823 /* getDefaultBaseType 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 2009 /* toValueSet */, 0, 138, 0, 138, 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 808d3d99de5..8cc7473379c 100644 --- a/turbo-ext/src/generated/ResourceType.h +++ b/turbo-ext/src/generated/ResourceType.h @@ -72,7 +72,9 @@ inline constexpr char strings[] = "position\0" /* 435 */ "PHPStan\\Type\\NativeTypePosition\0" /* 444 */ "toNativePhpTypeNode\0" /* 476 */ - "hasTemplateOrLateResolvableType"; /* 496 */ + "hasTemplateOrLateResolvableType\0" /* 496 */ + "PHPStan\\Type\\ValueSet\0" /* 528 */ + "toValueSet"; /* 550 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ @@ -99,6 +101,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(46, 0, 383), /* toPhpDocNode return */ reg::packed(435, 0, 444), /* toNativePhpTypeNode $position */ reg::packed(46, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(46, 0, 528), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -125,6 +128,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 368 /* getFiniteTypes */, 0, 2 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 550 /* toValueSet */, 0, 25, 0, 25, 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 a685efab6cc..472bbb9693e 100644 --- a/turbo-ext/src/generated/StaticType.h +++ b/turbo-ext/src/generated/StaticType.h @@ -236,7 +236,9 @@ inline constexpr char strings[] = "position\0" /* 3318 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3327 */ "toNativePhpTypeNode\0" /* 3359 */ - "hasTemplateOrLateResolvableType"; /* 3379 */ + "hasTemplateOrLateResolvableType\0" /* 3379 */ + "PHPStan\\Type\\ValueSet\0" /* 3411 */ + "toValueSet"; /* 3433 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 16), /* __construct $classReflection */ reg::packed(51, MAY_BE_NULL, 66, false, false, 84), /* __construct $subtractedType */ @@ -449,6 +451,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(101, 0, 3266), /* toPhpDocNode return */ reg::packed(3318, 0, 3327), /* toNativePhpTypeNode $position */ reg::packed(101, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(101, 0, 3411), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -589,6 +592,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 3251 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3433 /* toValueSet */, 0, 211, 0, 211, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StaticType diff --git a/turbo-ext/src/generated/StringType.h b/turbo-ext/src/generated/StringType.h index c7f38bb7d8e..fedeeef96ee 100644 --- a/turbo-ext/src/generated/StringType.h +++ b/turbo-ext/src/generated/StringType.h @@ -103,7 +103,9 @@ inline constexpr char strings[] = "position\0" /* 927 */ "PHPStan\\Type\\NativeTypePosition\0" /* 936 */ "toNativePhpTypeNode\0" /* 968 */ - "hasTemplateOrLateResolvableType"; /* 988 */ + "hasTemplateOrLateResolvableType\0" /* 988 */ + "PHPStan\\Type\\ValueSet\0" /* 1020 */ + "toValueSet"; /* 1042 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 18), /* describe $level */ reg::packed(46, MAY_BE_STRING), /* describe return */ @@ -168,6 +170,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(46, 0, 875), /* toPhpDocNode return */ reg::packed(927, 0, 936), /* toNativePhpTypeNode $position */ reg::packed(46, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(46, 0, 1020), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -220,6 +223,7 @@ inline constexpr sigtab::Sig arithmeticPower = { { 859 /* arithmeticPower */, 2, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1042 /* toValueSet */, 0, 63, 0, 63, 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 461c5a3467f..f7b46d6ed73 100644 --- a/turbo-ext/src/generated/UnionType.h +++ b/turbo-ext/src/generated/UnionType.h @@ -275,7 +275,9 @@ inline constexpr char strings[] = "PHPStan\\Type\\NativeTypePosition\0" /* 3807 */ "toNativePhpTypeNode\0" /* 3839 */ "notBenevolentPickFromTypes\0" /* 3859 */ - "hasTemplateOrLateResolvableType"; /* 3886 */ + "hasTemplateOrLateResolvableType\0" /* 3886 */ + "PHPStan\\Type\\ValueSet\0" /* 3918 */ + "toValueSet"; /* 3940 */ 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 */ @@ -534,6 +536,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(3713, MAY_BE_CALLABLE), /* notBenevolentPickFromTypes $getValues */ reg::packed(35, MAY_BE_ARRAY), /* notBenevolentPickFromTypes return */ reg::packed(35, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(35, 0, 3918), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -693,6 +696,7 @@ inline constexpr sigtab::Sig toPhpDocNode = { { 3785 /* toPhpDocNode */, 0, 252, inline constexpr sigtab::Sig toNativePhpTypeNode = { { 3839 /* toNativePhpTypeNode */, 1, 253, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig notBenevolentPickFromTypes = { { 3859 /* notBenevolentPickFromTypes */, 1, 254, 1, 255, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig hasTemplateOrLateResolvableType = { { 3886 /* hasTemplateOrLateResolvableType */, 0, 256, 0, 256, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 3940 /* toValueSet */, 0, 257, 0, 257, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::UnionType diff --git a/turbo-ext/src/support.cpp b/turbo-ext/src/support.cpp index e34c9b87288..9cd2b81e824 100644 --- a/turbo-ext/src/support.cpp +++ b/turbo-ext/src/support.cpp @@ -415,6 +415,7 @@ static const pt_class_template pt_class_templates[PT_CLASS_COUNT] = { /* PT_CLASS_NATIVE_PHP_TYPE_NODE_HELPER */ {"nativePhpTypeNodeHelper", "PHPStan\\Type\\NativePhpTypeNodeHelper"}, /* PT_CLASS_FULLY_QUALIFIED_NAME_NODE */ {"fullyQualifiedNameNode", "PHPStan\\PhpDocParser\\Ast\\Type\\FullyQualifiedNameNode"}, /* PT_CLASS_TYPE_NODE_DESCRIBER */ {"typeNodeDescriber", "PHPStan\\Type\\TypeNodeDescriber"}, + /* PT_CLASS_VALUE_SET */ {"valueSet", "PHPStan\\Type\\ValueSet"}, }; zend_class_entry *pt_class(int idx) diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index c1cf08062be..ba1eb89093f 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -519,6 +519,8 @@ enum { /* the class names of Type::toPhpDocNode() and how describe() prints them */ PT_CLASS_FULLY_QUALIFIED_NAME_NODE, PT_CLASS_TYPE_NODE_DESCRIBER, + /* the values a type holds, Type::toValueSet() */ + PT_CLASS_VALUE_SET, PT_CLASS_COUNT }; From 1268f37b9838b7d95c8dfd2e4900382ca6fde701 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 20:59:08 +0000 Subject: [PATCH 03/15] Compute %, intdiv, bitwise operators and negation on NumberSet Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 6 - src/Type/NumberSet.php | 276 ++++++++++ src/Type/OperatorTypeHelper.php | 356 +------------ turbo-ext/src/IetrSupport.h | 7 + turbo-ext/src/NumberSet.cpp | 296 ++++++++++ turbo-ext/src/OperatorTypeHelper.cpp | 534 ++----------------- turbo-ext/src/generated/NumberSet.h | 188 ++++--- turbo-ext/src/generated/OperatorTypeHelper.h | 148 ++--- 8 files changed, 831 insertions(+), 980 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index aaf043221ee..6fa1943f64c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1507,12 +1507,6 @@ parameters: 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: 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/Type/NumberSet.php b/src/Type/NumberSet.php index b3cbbd26892..799874e56d3 100644 --- a/src/Type/NumberSet.php +++ b/src/Type/NumberSet.php @@ -7,11 +7,13 @@ use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; +use function abs; use function count; use function in_array; use function intdiv; use function is_finite; use function is_float; +use function is_infinite; use function is_int; use function is_nan; use function max; @@ -144,6 +146,131 @@ public function divide(self $right, PhpVersion $phpVersion): Type return $this->calculate('/', $right, $phpVersion); } + /** + * Models intdiv() of integers, the operands converted with toInteger(). The divisor's + * negative and positive parts are divided separately (dividing by zero throws), each at + * the corners of the operand intervals: truncated division is monotonic in both operands + * as long as the divisor keeps its sign. + */ + public function intDivide(self $right, PhpVersion $phpVersion): Type + { + $resultTypes = []; + foreach ($this->integerIntervals as [$numeratorMin, $numeratorMax]) { + foreach ($right->integerIntervals as [$divisorMin, $divisorMax]) { + $numerator = [$numeratorMin ?? -INF, $numeratorMax ?? INF]; + $divisorMin ??= -INF; + $divisorMax ??= INF; + if ($divisorMin < 0) { + $resultTypes[] = self::intDivideIntervals($numerator, [$divisorMin, min($divisorMax, -1)]); + } + if ($divisorMax <= 0) { + continue; + } + + $resultTypes[] = self::intDivideIntervals($numerator, [max($divisorMin, 1), $divisorMax]); + } + } + + if ($resultTypes === []) { + return new ErrorType(); + } + + return TypeCombinator::union(...$resultTypes); + } + + /** + * Models the % operator on integers, the operands converted with toInteger(). 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. + */ + public function modulo(self $right, PhpVersion $phpVersion): Type + { + [$leftMin, $leftMax] = $this->getIntegerHull(); + [$divisorMin, $divisorMax] = $right->getIntegerHull(); + $maxMagnitude = self::getMaxModuloMagnitude($divisorMin, $divisorMax); + + 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); + } + + /** + * Models the & operator on numbers, the operands 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>. + */ + public function bitwiseAnd(self $right, PhpVersion $phpVersion): Type + { + $leftBounds = $this->getNonNegativeIntegerBounds(); + $rightBounds = $right->getNonNegativeIntegerBounds(); + if ($leftBounds === null && $rightBounds === null) { + return new IntegerType(); + } + + $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)); + } + + /** Models the | operator on numbers, the operands converted with toNumber(). */ + public function bitwiseOr(self $right, PhpVersion $phpVersion): Type + { + return $this->bitwiseOrXor($right); + } + + /** Models the ^ operator on numbers, the operands converted with toNumber(). */ + public function bitwiseXor(self $right, PhpVersion $phpVersion): Type + { + return $this->bitwiseOrXor($right); + } + + /** Models the unary - operator: each member negates on its own. */ + public function negate(PhpVersion $phpVersion): Type + { + // -PHP_INT_MIN overflows into a float, as the product with -1 does + $types = []; + if ($this->integerIntervals !== []) { + $types[] = (new self($this->integerIntervals, [], false))->multiply(new self([[-1, -1]], [], false), $phpVersion); + } + foreach ($this->floatIntervals as [$min, $max]) { + if ($min === -INF && $max === INF && $this->nan) { + return TypeCombinator::union(new FloatType(), ...$types); + } + + // a constant keeps the sign of its zero + $types[] = $min === $max ? new ConstantFloatType(-$min) : FloatRangeType::fromInterval(-$max, -$min); + } + if ($this->nan) { + $types[] = new ConstantFloatType(NAN); + } + + return TypeCombinator::union(...$types); + } + /** Models the << operator; a negative shift is an error. */ public function shiftLeft(self $right, PhpVersion $phpVersion): Type { @@ -562,6 +689,155 @@ private static function toIntBound(int|float $value): ?int return (int) $value; } + /** + * 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 bitwiseOrXor(self $right): Type + { + $leftBounds = $this->getNonNegativeIntegerBounds(); + $rightBounds = $right->getNonNegativeIntegerBounds(); + if ($leftBounds === null || $rightBounds === null) { + return new IntegerType(); + } + if ($leftBounds[1] === null || $rightBounds[1] === null) { + return IntegerRangeType::fromInterval(0, null); + } + + return IntegerRangeType::fromInterval(0, self::allBitsMask(max($leftBounds[1], $rightBounds[1]))); + } + + /** + * [min, max] of a set of one non-negative integer interval, with a null max when it has + * no finite upper bound. + * + * @return array{int, int|null}|null + */ + private function getNonNegativeIntegerBounds(): ?array + { + if ($this->floatIntervals !== [] || $this->nan || count($this->integerIntervals) !== 1) { + return null; + } + + [$min, $max] = $this->integerIntervals[0]; + if ($min === null || $min < 0) { + return null; + } + + return [$min, $max]; + } + + /** + * 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; + } + + /** + * The lowest and highest integer of the set, null for an unbounded side or no integers. + * + * @return array{int|null, int|null} + */ + private function getIntegerHull(): array + { + $min = null; + $max = null; + $unboundedMin = $this->integerIntervals === []; + $unboundedMax = $this->integerIntervals === []; + foreach ($this->integerIntervals as [$innerMin, $innerMax]) { + 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]; + } + + /** + * Highest possible absolute value of `$x % $divisor`, one less than the largest possible + * absolute value of the divisor, or null when there is no such bound. A divisor reaching + * PHP_INT_MIN counts as unbounded: 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; + } + + /** + * Unbounded ends are -INF and INF. + * + * @param array{int|float, int|float} $numerator + * @param array{int|float, int|float} $divisor + */ + private static function intDivideIntervals(array $numerator, array $divisor): Type + { + $results = []; + foreach ($numerator as $numeratorBound) { + foreach ($divisor as $divisorBound) { + $results[] = self::intDivideBound($numeratorBound, $divisorBound); + } + } + + $min = min($results); + $max = max($results); + + return IntegerRangeType::fromInterval( + is_infinite($min) ? null : (int) $min, + is_infinite($max) ? null : (int) $max, + ); + } + + private static function intDivideBound(int|float $numerator, int|float $divisor): int|float + { + if (is_infinite($numerator)) { + if (is_infinite($divisor)) { + // the interval 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); + } + /** * @param '+'|'-'|'*'|'/' $operator * @param array{float, float, bool} $left [min, max, whether an infinite bound is a value] diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index fd7439adc42..02307452994 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -17,23 +17,13 @@ use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; -use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use function abs; use function array_merge; use function count; use function in_array; -use function intdiv; -use function is_float; -use function is_infinite; use function is_int; use function is_numeric; -use function max; -use function min; -use const INF; -use const PHP_INT_MAX; -use const PHP_INT_MIN; /** * The operators of Type::arithmetic*(), Type::bitwise*(), Type::stringConcatenate() and @@ -236,12 +226,9 @@ public static function bitwiseAnd(Type $leftType, Type $rightType, PhpVersion $p return new ErrorType(); } - $bitwiseAndRange = self::computeBitwiseAndRange($leftNumberType, $rightNumberType); - if ($bitwiseAndRange !== null) { - return $bitwiseAndRange; - } + $anyInteger = new NumberSet([[null, null]], [], false); - return new IntegerType(); + return ($leftNumberType->toNumberSet() ?? $anyInteger)->bitwiseAnd($rightNumberType->toNumberSet() ?? $anyInteger, $phpVersion); } public static function bitwiseOr(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type @@ -289,12 +276,9 @@ public static function bitwiseOr(Type $leftType, Type $rightType, PhpVersion $ph return new ErrorType(); } - $bitwiseOrRange = self::computeBitwiseOrXorRange($leftNumberType, $rightNumberType); - if ($bitwiseOrRange !== null) { - return $bitwiseOrRange; - } + $anyInteger = new NumberSet([[null, null]], [], false); - return new IntegerType(); + return ($leftNumberType->toNumberSet() ?? $anyInteger)->bitwiseOr($rightNumberType->toNumberSet() ?? $anyInteger, $phpVersion); } public static function bitwiseXor(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type @@ -342,12 +326,9 @@ public static function bitwiseXor(Type $leftType, Type $rightType, PhpVersion $p return new ErrorType(); } - $bitwiseXorRange = self::computeBitwiseOrXorRange($leftNumberType, $rightNumberType); - if ($bitwiseXorRange !== null) { - return $bitwiseXorRange; - } + $anyInteger = new NumberSet([[null, null]], [], false); - return new IntegerType(); + return ($leftNumberType->toNumberSet() ?? $anyInteger)->bitwiseXor($rightNumberType->toNumberSet() ?? $anyInteger, $phpVersion); } /** @@ -535,100 +516,11 @@ public static function modulo(Type $leftType, Type $rightType, PhpVersion $phpVe } } - $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; - } + $anyInteger = new NumberSet([[null, null]], [], false); + $leftNumbers = self::getIntegerNumberSet($leftType->toInteger()) ?? $anyInteger; + $rightNumbers = self::getIntegerNumberSet($rightType->toInteger()) ?? $anyInteger; - 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 holds anything but integers. - */ - private static function getIntegerBounds(Type $type): ?array - { - $intervals = self::getIntegerIntervals($type); - if ($intervals === null) { - return null; - } - - $min = null; - $max = null; - $unboundedMin = false; - $unboundedMax = false; - foreach ($intervals as [$innerMin, $innerMax]) { - 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, - ]; + return $leftNumbers->modulo($rightNumbers, $phpVersion); } /** @@ -1119,88 +1011,6 @@ private static function optimizeScalarType(Type $type): Type 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 - { - $intervals = self::getIntegerIntervals($type); - if ($intervals === null || count($intervals) !== 1) { - return null; - } - - [$min, $max] = $intervals[0]; - if ($min === null || $min < 0) { - return null; - } - - return [$min, $max]; - } - - /** - * 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 */ @@ -1288,34 +1098,6 @@ private static function resolveCommonMath(string $operator, Type $leftType, Type return $resultType; } - 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 integer a constant operand of %, <<, >>, &, | and ^ becomes: numeric * strings out of the int range saturate like an (int) cast instead of @@ -1353,126 +1135,40 @@ private static function getNeverType(Type $leftType, Type $rightType): Type public static function negate(Type $type, PhpVersion $phpVersion): Type { - // each member negates on its own: -(int<1, 5>|1.5) is int<-5, -1>|-1.5 $number = $type->toNumber(); - if ($number instanceof UnionType) { - $negatedTypes = []; - foreach ($number->getTypes() as $innerType) { - $negatedTypes[] = self::negate($innerType, $phpVersion); - } - - $negated = TypeCombinator::union(...$negatedTypes); - if ($number instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($negated); - } - - return $negated; + $numberSet = $number->toNumberSet(); + if ($numberSet === null) { + return $number; } - $negated = self::negateNumber($type); - if ($negated instanceof IntegerRangeType) { - return self::multiply($type, new ConstantIntegerType(-1), $phpVersion); - } - if ($negated instanceof FloatRangeType) { - return FloatRangeType::fromInterval(-$negated->getMax(), -$negated->getMin()); + $negated = $numberSet->negate($phpVersion); + if ($number instanceof BenevolentUnionType) { + return TypeUtils::toBenevolentUnion($negated); } 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) { + $numerator = self::getIntegerNumberSet($numeratorType->toInteger()); + $divisor = self::getIntegerNumberSet($divisorType->toInteger()); + if ($numerator === null || $divisor === 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); + return $numerator->intDivide($divisor, $phpVersion); } - /** - * Unbounded ends are represented by -INF and INF. - * - * @return list|null - */ - private static function getIntervals(Type $type): ?array + /** The numbers of a type that holds integers and nothing else, or null. */ + private static function getIntegerNumberSet(Type $type): ?NumberSet { - $integerIntervals = self::getIntegerIntervals($type); - if ($integerIntervals === null) { - return $type->isInteger()->yes() ? [[-INF, INF]] : null; - } - - $intervals = []; - foreach ($integerIntervals as [$min, $max]) { - $intervals[] = [$min ?? -INF, $max ?? INF]; - } - - 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; + $intervals = self::getIntegerIntervals($type); + if ($intervals === null) { + return $type->isInteger()->yes() ? new NumberSet([[null, null]], [], false) : null; } - return intdiv((int) $numerator, (int) $divisor); + return new NumberSet($intervals, [], false); } } diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h index 7ae6d97e218..a15ddd35101 100644 --- a/turbo-ext/src/IetrSupport.h +++ b/turbo-ext/src/IetrSupport.h @@ -672,5 +672,12 @@ inline void setNullableLong(zval *out, NullableLong value) * data: `kind` IETR_PLUS, _MINUS, _MUL, _DIV, _SHIFT_LEFT or _SHIFT_RIGHT; * UNDEF = pending exception */ zv::Val pt_number_set_calculate(phpstanturbo::ietr::IetrBinaryKind kind, const phpstanturbo::NumberSetData &left, const phpstanturbo::NumberSetData &right, zval *phpVersion); +/* NumberSet::intDivide() / modulo() / bitwiseAnd() & co. / negate() on the + * sets' data (NumberSet.cpp); `kind` IETR_BITWISE_AND, _OR or _XOR; UNDEF = + * pending exception */ +zv::Val pt_number_set_int_divide(const phpstanturbo::NumberSetData &left, const phpstanturbo::NumberSetData &right); +zv::Val pt_number_set_modulo(const phpstanturbo::NumberSetData &left, const phpstanturbo::NumberSetData &right); +zv::Val pt_number_set_bitwise(phpstanturbo::ietr::IetrBinaryKind kind, const phpstanturbo::NumberSetData &left, const phpstanturbo::NumberSetData &right); +zv::Val pt_number_set_negate(const phpstanturbo::NumberSetData &set, zval *phpVersion); #endif diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp index 488e3cc7b2d..6aabcbcd05d 100644 --- a/turbo-ext/src/NumberSet.cpp +++ b/turbo-ext/src/NumberSet.cpp @@ -728,6 +728,248 @@ class NumberSetMath return 1; } + +public: + /* Mirrors intDivide(). */ + static zv::Val intDivide(const NumberSetData &left, const NumberSetData &right) + { + zv::Arr resultTypes = zv::Arr::create((uint32_t) (left.integerIntervals.size() * right.integerIntervals.size() * 2)); + for (const NumberSetData::IntegerInterval &numerator : left.integerIntervals) { + Bound numeratorMin = numerator.min.isNull ? Bound::infinity(-1) : Bound::of(numerator.min.value); + Bound numeratorMax = numerator.max.isNull ? Bound::infinity(1) : Bound::of(numerator.max.value); + for (const NumberSetData::IntegerInterval &divisor : right.integerIntervals) { + Bound divisorMin = divisor.min.isNull ? Bound::infinity(-1) : Bound::of(divisor.min.value); + Bound divisorMax = divisor.max.isNull ? Bound::infinity(1) : Bound::of(divisor.max.value); + if (lessThanBound(divisorMin, Bound::of(0))) { + Bound minusOne = Bound::of(-1); + IETR_VAL(negative, intDivideIntervals(numeratorMin, numeratorMax, divisorMin, lessThanBound(minusOne, divisorMax) ? minusOne : divisorMax)); + resultTypes.push(std::move(negative)); + } + if (!lessThanBound(Bound::of(0), divisorMax)) { + continue; + } + + Bound one = Bound::of(1); + IETR_VAL(positive, intDivideIntervals(numeratorMin, numeratorMax, lessThanBound(divisorMin, one) ? one : divisorMin, divisorMax)); + resultTypes.push(std::move(positive)); + } + } + + if (zend_hash_num_elements(resultTypes.table()) == 0) { + return newErrorType(); + } + + return unionOf(resultTypes); + } + + /* Mirrors intDivideIntervals(). */ + static zv::Val intDivideIntervals(Bound numeratorMin, Bound numeratorMax, Bound divisorMin, Bound divisorMax) + { + Bound results[4] = { + intDivideBound(numeratorMin, divisorMin), + intDivideBound(numeratorMin, divisorMax), + intDivideBound(numeratorMax, divisorMin), + intDivideBound(numeratorMax, divisorMax), + }; + Bound min = results[0]; + Bound max = results[0]; + for (const Bound &result : results) { + if (lessThanBound(result, min)) min = result; + if (lessThanBound(max, result)) max = result; + } + + return fromInterval( + min.inf != 0 ? NullableLong::null() : NullableLong::of(min.value), + max.inf != 0 ? NullableLong::null() : NullableLong::of(max.value) + ); + } + + /* Mirrors intDivideBound(). */ + static Bound intDivideBound(Bound numerator, Bound divisor) + { + if (numerator.inf != 0) { + if (divisor.inf != 0) { + // the interval 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); + } + + /* Mirrors modulo(). */ + static zv::Val modulo(const NumberSetData &left, const NumberSetData &right) + { + NullableLong leftMin, leftMax, divisorMin, divisorMax; + getIntegerHull(left, leftMin, leftMax); + getIntegerHull(right, divisorMin, divisorMax); + NullableLong maxMagnitude = getMaxModuloMagnitude(divisorMin, divisorMax); + + 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 getIntegerHull(). */ + static void getIntegerHull(const NumberSetData &set, NullableLong &outMin, NullableLong &outMax) + { + NullableLong min = NullableLong::null(); + NullableLong max = NullableLong::null(); + bool unboundedMin = set.integerIntervals.empty(); + bool unboundedMax = set.integerIntervals.empty(); + for (const NumberSetData::IntegerInterval &interval : set.integerIntervals) { + if (interval.min.isNull) { + unboundedMin = true; + } else if (min.isNull || interval.min.value < min.value) { + min = interval.min; + } + + if (interval.max.isNull) { + unboundedMax = true; + } else if (max.isNull || interval.max.value > max.value) { + max = interval.max; + } + } + + outMin = unboundedMin ? NullableLong::null() : min; + outMax = unboundedMax ? NullableLong::null() : max; + } + + /* Mirrors getMaxModuloMagnitude(). */ + 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); + } + + /* Mirrors bitwiseAnd(), bitwiseOr() and bitwiseXor(). */ + static zv::Val bitwise(IetrBinaryKind kind, const NumberSetData &left, const NumberSetData &right) + { + zend_long leftMin = 0, rightMin = 0; + NullableLong leftMax = NullableLong::null(), rightMax = NullableLong::null(); + bool leftBounds = getNonNegativeIntegerBounds(left, leftMin, leftMax); + bool rightBounds = getNonNegativeIntegerBounds(right, rightMin, rightMax); + if (kind == IETR_BITWISE_AND) { + if (!leftBounds && !rightBounds) { + return newIntegerType(); + } + + bool anyMax = false; + zend_long minOfMax = 0; + if (leftBounds && !leftMax.isNull) { + minOfMax = leftMax.value; + anyMax = true; + } + if (rightBounds && !rightMax.isNull) { + minOfMax = anyMax && minOfMax <= rightMax.value ? minOfMax : rightMax.value; + anyMax = true; + } + + return fromInterval(NullableLong::of(0), anyMax ? NullableLong::of(minOfMax) : NullableLong::null()); + } + + /* Mirrors bitwiseOrXor(). */ + if (!leftBounds || !rightBounds) { + return newIntegerType(); + } + 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 getNonNegativeIntegerBounds(): false for its null */ + static bool getNonNegativeIntegerBounds(const NumberSetData &set, zend_long &min, NullableLong &max) + { + if (!set.floatIntervals.empty() || set.nan || set.integerIntervals.size() != 1) return false; + const NumberSetData::IntegerInterval &interval = set.integerIntervals[0]; + if (interval.min.isNull || interval.min.value < 0) return false; + min = interval.min.value; + max = interval.max; + return true; + } + + /* 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; + } + + /* Mirrors negate(). */ + static zv::Val negate(const NumberSetData &set, zval *phpVersion) + { + // -PHP_INT_MIN overflows into a float, as the product with -1 does + zv::Arr types = zv::Arr::create((uint32_t) (set.floatIntervals.size() + 2)); + if (!set.integerIntervals.empty()) { + NumberSetData integers; + integers.integerIntervals = set.integerIntervals; + NumberSetData minusOne; + minusOne.integerIntervals.push_back({NullableLong::of(-1), NullableLong::of(-1)}); + IETR_VAL(product, calculate(IETR_MUL, integers, minusOne, phpVersion)); + types.push(std::move(product)); + } + for (const NumberSetData::FloatInterval &interval : set.floatIntervals) { + if (interval.min == -std::numeric_limits::infinity() && interval.max == std::numeric_limits::infinity() && set.nan) { + // the twin's union(new FloatType(), ...$types): the FloatType absorbs the floats so far + IETR_VAL(floatType, newFloatType()); + types.push(std::move(floatType)); + return unionOf(types); + } + + // a constant keeps the sign of its zero + IETR_VAL(negated, interval.min == interval.max ? newConstantFloatType(-interval.min) : pt_float_range_from_interval(-interval.max, -interval.min)); + types.push(std::move(negated)); + } + if (set.nan) { + IETR_VAL(nan, newConstantFloatType(std::numeric_limits::quiet_NaN())); + types.push(std::move(nan)); + } + + return unionOf(types); + } }; /* }}} */ @@ -742,6 +984,26 @@ zv::Val pt_number_set_new(const NumberSetData &data) return NumberSet::create(data); } +zv::Val pt_number_set_int_divide(const NumberSetData &left, const NumberSetData &right) +{ + return phpstanturbo::NumberSetMath::intDivide(left, right); +} + +zv::Val pt_number_set_modulo(const NumberSetData &left, const NumberSetData &right) +{ + return phpstanturbo::NumberSetMath::modulo(left, right); +} + +zv::Val pt_number_set_bitwise(phpstanturbo::ietr::IetrBinaryKind kind, const NumberSetData &left, const NumberSetData &right) +{ + return phpstanturbo::NumberSetMath::bitwise(kind, left, right); +} + +zv::Val pt_number_set_negate(const NumberSetData &set, zval *phpVersion) +{ + return phpstanturbo::NumberSetMath::negate(set, phpVersion); +} + zv::Val pt_number_set_calculate(phpstanturbo::ietr::IetrBinaryKind kind, const NumberSetData &left, const NumberSetData &right, zval *phpVersion) { return phpstanturbo::NumberSetMath::calculate(kind, left, right, phpVersion); @@ -768,6 +1030,28 @@ bool pt_type_to_number_set(zval *type, NumberSetData &out, bool &has) #define PT_THIS NumberSet(Z_OBJ_P(ZEND_THIS)) +/* intDivide() & co.: (self $right, PhpVersion $phpVersion) */ +template +static void pt_ns_integer_operator(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(right), pt_ce_number_set))) { + zend_type_error("%s::%s(): Argument #1 ($right) must be of type %s, %s given", ZSTR_VAL(pt_ce_number_set->name), get_active_function_name(), ZSTR_VAL(pt_ce_number_set->name), zend_zval_value_name(right)); + RETURN_THROWS(); + } + NumberSetData left, rightData; + if (UNEXPECTED(!PT_THIS.read(left) || !NumberSet(Z_OBJ_P(right)).read(rightData))) RETURN_THROWS(); + using phpstanturbo::NumberSetMath; + switch (Op) { + case 0: PT_RETURN_VAL(NumberSetMath::intDivide(left, rightData)); + case 1: PT_RETURN_VAL(NumberSetMath::modulo(left, rightData)); + case 2: PT_RETURN_VAL(NumberSetMath::bitwise(phpstanturbo::ietr::IETR_BITWISE_AND, left, rightData)); + case 3: PT_RETURN_VAL(NumberSetMath::bitwise(phpstanturbo::ietr::IETR_BITWISE_OR, left, rightData)); + default: PT_RETURN_VAL(NumberSetMath::bitwise(phpstanturbo::ietr::IETR_BITWISE_XOR, left, rightData)); + } +} + /* union() / intersect(): (self $other) — PHP's max()/min() keep the first of * equal values, as `b > a ? b : a` does */ template @@ -858,6 +1142,18 @@ PT_MINIT_REGISTRATION(pt_register_number_set) cls.method(sigs::subtract, pt_ns_operator); cls.method(sigs::multiply, pt_ns_operator); cls.method(sigs::divide, pt_ns_operator); + cls.method(sigs::intDivide, pt_ns_integer_operator<0>); + cls.method(sigs::modulo, pt_ns_integer_operator<1>); + cls.method(sigs::bitwiseAnd, pt_ns_integer_operator<2>); + cls.method(sigs::bitwiseOr, pt_ns_integer_operator<3>); + cls.method(sigs::bitwiseXor, pt_ns_integer_operator<4>); + cls.method(sigs::negate, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *phpVersion; + if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); + NumberSetData set; + if (UNEXPECTED(!PT_THIS.read(set))) RETURN_THROWS(); + PT_RETURN_VAL(phpstanturbo::NumberSetMath::negate(set, phpVersion)); + }); cls.method(sigs::shiftLeft, pt_ns_operator); cls.method(sigs::shiftRight, pt_ns_operator); diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index a95dfd96a58..503fb340c4d 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -309,13 +309,14 @@ class OperatorTypeHelper 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(); + NumberSetData leftNumbers, rightNumbers; + bool leftHas = false, rightHas = false; + if (UNEXPECTED(!pt_type_to_number_set(leftNumberType.raw(), leftNumbers, leftHas))) return zv::Val(); + if (UNEXPECTED(!pt_type_to_number_set(rightNumberType.raw(), rightNumbers, rightHas))) return zv::Val(); + if (!leftHas) leftNumbers = anyInteger(); + if (!rightHas) rightNumbers = anyInteger(); + + return pt_number_set_bitwise(kind, leftNumbers, rightNumbers); } /* Mirrors getFiniteOrConstantScalarTypes() with the operation of `kind`: @@ -679,111 +680,15 @@ class OperatorTypeHelper } } - 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; + NumberSetData leftNumbers, rightNumbers; { 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); - } - - /* 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 holds anything but - * integers. false = pending exception */ - [[nodiscard]] static bool getIntegerBounds(zval *type, bool &has, NullableLong &outMin, NullableLong &outMax) - { - std::vector intervals; - if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return false; - if (!has) return true; - - NullableLong min = NullableLong::null(); - NullableLong max = NullableLong::null(); - bool unboundedMin = false; - bool unboundedMax = false; - for (const NumberSetData::IntegerInterval &interval : intervals) { - if (interval.min.isNull) { - unboundedMin = true; - } else if (min.isNull || interval.min.value < min.value) { - min = interval.min; - } - - if (interval.max.isNull) { - unboundedMax = true; - } else if (max.isNull || interval.max.value > max.value) { - max = interval.max; - } + if (UNEXPECTED(!getIntegerNumberSetOrAny(leftInteger.raw(), leftNumbers))) return zv::Val(); + IETR_VAL(rightInteger, callOn(rightType, mToInteger)); + if (UNEXPECTED(!getIntegerNumberSetOrAny(rightInteger.raw(), rightNumbers))) return zv::Val(); } - outMin = unboundedMin ? NullableLong::null() : min; - outMax = unboundedMax ? NullableLong::null() : max; - return true; + return pt_number_set_modulo(leftNumbers, rightNumbers); } /* Mirrors getIntegerIntervals(): the integer intervals of a type that @@ -832,21 +737,6 @@ class OperatorTypeHelper return zv::Val(std::move(pair)); } - /* getIntegerBounds() as the private method returns it: the [min, max] - * pair or null; UNDEF = pending exception */ - static zv::Val getIntegerBoundsValue(zval *type) - { - 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)); - } - /* Mirrors add(). */ static zv::Val add(zval *leftType, zval *rightType, zval *phpVersion) { @@ -1286,97 +1176,6 @@ class OperatorTypeHelper 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) - { - std::vector intervals; - bool has = false; - if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return -1; - if (!has || intervals.size() != 1) return 0; - - const NumberSetData::IntegerInterval &interval = intervals[0]; - if (interval.min.isNull || interval.min.value < 0) return 0; - - min = interval.min.value; - max = interval.max; - return 1; - } - - /* Mirrors getNonNegativeIntegerBounds() as the PHP value it returns: - * [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() */ @@ -1464,76 +1263,19 @@ class OperatorTypeHelper /* {{{ 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, zval *phpVersion) { - // each member negates on its own: -(int<1, 5>|1.5) is int<-5, -1>|-1.5 IETR_VAL(number, callOn(type, mToNumber)); - if (isA(number.raw(), pt_ce_union_type)) { - IETR_VAL(innerTypes, callOn(number.raw(), mGetTypes)); - if (UNEXPECTED(!requireArray(innerTypes.raw()))) return zv::Val(); - zv::Arr negatedTypes = zv::Arr::create(zend_hash_num_elements(Z_ARRVAL_P(innerTypes.raw()))); - for (zv::ArrayEntry entry : zv::ArrRef(innerTypes.raw())) { - IETR_VAL(negatedType, negate(entry.value().raw(), phpVersion)); - negatedTypes.push(std::move(negatedType)); - } - - IETR_VAL(negatedUnion, unionOf(negatedTypes)); - if (isA(number.raw(), pt_ce_benevolent_union_type)) return toBenevolentUnion(negatedUnion.raw()); - - return negatedUnion; + NumberSetData numberSet; + bool has = false; + if (UNEXPECTED(!pt_type_to_number_set(number.raw(), numberSet, has))) return zv::Val(); + if (!has) { + return number; } - 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(), phpVersion); - } - if (isA(negated.raw(), pt_ce_float_range_type)) { - double min, max; - if (UNEXPECTED(!pt_float_range_bounds(Z_OBJ_P(negated.raw()), min, max))) return zv::Val(); - return pt_float_range_from_interval(-max, -min); - } + IETR_VAL(negated, pt_number_set_negate(numberSet, phpVersion)); + if (isA(number.raw(), pt_ce_benevolent_union_type)) return toBenevolentUnion(negated.raw()); return negated; } @@ -1569,193 +1311,69 @@ class OperatorTypeHelper /* {{{ 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; + NumberSetData numerator, divisor; + bool numeratorHas = false, divisorHas = false; { IETR_VAL(numeratorInteger, callOn(numeratorType, mToInteger)); - if (UNEXPECTED(!getIntervals(numeratorInteger.raw(), numeratorIntervals, numeratorKnown))) return zv::Val(); + if (UNEXPECTED(!getIntegerNumberSet(numeratorInteger.raw(), numerator, numeratorHas))) return zv::Val(); IETR_VAL(divisorInteger, callOn(divisorType, mToInteger)); - if (UNEXPECTED(!getIntervals(divisorInteger.raw(), divisorIntervals, divisorKnown))) return zv::Val(); + if (UNEXPECTED(!getIntegerNumberSet(divisorInteger.raw(), divisor, divisorHas))) return zv::Val(); } - if (!numeratorKnown || !divisorKnown) { + if (!numeratorHas || !divisorHas) { 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); + return pt_number_set_int_divide(numerator, divisor); } - /* Mirrors getIntervals(): `known` false for the twin's null; false = + /* Mirrors getIntegerNumberSet(): `has` false for its null; false = * pending exception */ - [[nodiscard]] static bool getIntervals(zval *type, std::vector &intervals, bool &known) + [[nodiscard]] static bool getIntegerNumberSet(zval *type, NumberSetData &out, bool &has) { - known = false; - std::vector integerIntervals; - bool has = false; - if (UNEXPECTED(!getIntegerIntervals(type, integerIntervals, has))) return false; + std::vector intervals; + if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return false; if (!has) { zend_long isInteger = triOn(type, mIsInteger); if (UNEXPECTED(isInteger < 0)) return false; if (isInteger != PT_TRI_YES) return true; - intervals.push_back({ Bound::infinity(-1), Bound::infinity(1) }); - known = true; + out = anyInteger(); + has = true; return true; } - for (const NumberSetData::IntegerInterval &interval : integerIntervals) { - intervals.push_back({ - interval.min.isNull ? Bound::infinity(-1) : Bound::of(interval.min.value), - interval.max.isNull ? Bound::infinity(1) : Bound::of(interval.max.value), - }); - } - - known = true; + out.integerIntervals = std::move(intervals); 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) + /* getIntegerNumberSet() or any integer: the `?? $anyInteger` of the twin */ + [[nodiscard]] static bool getIntegerNumberSetOrAny(zval *type, NumberSetData &out) { - 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)); + bool has = false; + if (UNEXPECTED(!getIntegerNumberSet(type, out, has))) return false; + if (!has) out = anyInteger(); + return true; } - /* Mirrors divideIntervals(). */ - static zv::Val divideIntervals(Bound numeratorMin, Bound numeratorMax, Bound divisorMin, Bound divisorMax) + /* getIntegerNumberSet() as the private method returns it: a NumberSet or + * null; UNDEF = pending exception */ + static zv::Val getIntegerNumberSetValue(zval *type) { - 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) - ); + NumberSetData set; + bool has = false; + if (UNEXPECTED(!getIntegerNumberSet(type, set, has))) return zv::Val(); + if (!has) return zv::Val::null(); + return pt_number_set_new(set); } - /* Mirrors divideBounds(). */ - static Bound divideBounds(Bound numerator, Bound divisor) + /* new NumberSet([[null, null]], [], false) */ + static NumberSetData anyInteger() { - 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); + NumberSetData set; + set.integerIntervals.push_back({ NullableLong::null(), NullableLong::null() }); + return set; } /* }}} */ @@ -1765,7 +1383,6 @@ class OperatorTypeHelper } // namespace phpstanturbo using phpstanturbo::OperatorTypeHelper; -using phpstanturbo::NullableLong; /* {{{ direct entries */ @@ -1921,20 +1538,7 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) cls.method(sigs::getDivTypeFromTypes, OT_BINARY_GLUE(OperatorTypeHelper::getDivTypeFromTypes(leftType, rightType, phpVersion))); cls.method(sigs::modulo, OT_BINARY_GLUE(OperatorTypeHelper::modulo(leftType, rightType))); - cls.method(sigs::getMaxModuloMagnitude, [](INTERNAL_FUNCTION_PARAMETERS) { - 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::getIntegerNumberSet, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerNumberSetValue(type))); cls.method(sigs::getIntegerIntervals, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerIntervalsValue(type))); cls.method(sigs::add, OT_BINARY_GLUE(OperatorTypeHelper::add(leftType, rightType, phpVersion))); cls.method(sigs::arrayUnion, OT_BINARY_GLUE(OperatorTypeHelper::arrayUnion(leftType, rightType))); @@ -1952,15 +1556,6 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) 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))); - - 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_; @@ -1972,8 +1567,6 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) PT_RETURN_VAL(OperatorTypeHelper::resolveCommonMath(kind, leftType, rightType, phpVersion)); }); - cls.method(sigs::negateNumber, OT_TYPE_GLUE(OperatorTypeHelper::negateNumber(type))); - cls.method(sigs::getTypeFromValue, [](INTERNAL_FUNCTION_PARAMETERS) { zval *value; if (!zp::parse(execute_data, value)) RETURN_THROWS(); @@ -2005,27 +1598,6 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) 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); } diff --git a/turbo-ext/src/generated/NumberSet.h b/turbo-ext/src/generated/NumberSet.h index e4631834994..7894ace86f3 100644 --- a/turbo-ext/src/generated/NumberSet.h +++ b/turbo-ext/src/generated/NumberSet.h @@ -51,34 +51,49 @@ inline constexpr char strings[] = "subtract\0" /* 201 */ "multiply\0" /* 210 */ "divide\0" /* 219 */ - "shiftLeft\0" /* 226 */ - "shiftRight\0" /* 236 */ - "operator\0" /* 247 */ - "calculate\0" /* 256 */ - "hasAnyInteger\0" /* 266 */ - "countMembers\0" /* 280 */ - "isEmpty\0" /* 293 */ - "hasFloatRange\0" /* 301 */ - "toHull\0" /* 315 */ - "interval\0" /* 322 */ - "integerToFloatInterval\0" /* 331 */ - "splitIntegerDivisor\0" /* 354 */ - "left\0" /* 374 */ - "calculateIntegers\0" /* 379 */ - "value\0" /* 397 */ - "shift\0" /* 403 */ - "shiftLeftOverflows\0" /* 409 */ - "divideIntegers\0" /* 428 */ - "numerator\0" /* 443 */ - "divisor\0" /* 453 */ - "divideBound\0" /* 461 */ - "toIntBound\0" /* 473 */ - "calculateFloats\0" /* 484 */ - "calculateFloat\0" /* 500 */ - "bound\0" /* 515 */ - "min\0" /* 521 */ - "max\0" /* 525 */ - "getFloatBoundWithInnerNeighbour"; /* 529 */ + "intDivide\0" /* 226 */ + "modulo\0" /* 236 */ + "bitwiseAnd\0" /* 243 */ + "bitwiseOr\0" /* 254 */ + "bitwiseXor\0" /* 264 */ + "negate\0" /* 275 */ + "shiftLeft\0" /* 282 */ + "shiftRight\0" /* 292 */ + "operator\0" /* 303 */ + "calculate\0" /* 312 */ + "hasAnyInteger\0" /* 322 */ + "countMembers\0" /* 336 */ + "isEmpty\0" /* 349 */ + "hasFloatRange\0" /* 357 */ + "toHull\0" /* 371 */ + "interval\0" /* 378 */ + "integerToFloatInterval\0" /* 387 */ + "splitIntegerDivisor\0" /* 410 */ + "left\0" /* 430 */ + "calculateIntegers\0" /* 435 */ + "value\0" /* 453 */ + "shift\0" /* 459 */ + "shiftLeftOverflows\0" /* 465 */ + "divideIntegers\0" /* 484 */ + "numerator\0" /* 499 */ + "divisor\0" /* 509 */ + "divideBound\0" /* 517 */ + "toIntBound\0" /* 529 */ + "bitwiseOrXor\0" /* 540 */ + "getNonNegativeIntegerBounds\0" /* 553 */ + "allBitsMask\0" /* 581 */ + "getIntegerHull\0" /* 593 */ + "divisorMin\0" /* 608 */ + "divisorMax\0" /* 619 */ + "getMaxModuloMagnitude\0" /* 630 */ + "intDivideIntervals\0" /* 652 */ + "intDivideBound\0" /* 671 */ + "calculateFloats\0" /* 686 */ + "calculateFloat\0" /* 702 */ + "bound\0" /* 717 */ + "min\0" /* 723 */ + "max\0" /* 727 */ + "getFloatBoundWithInnerNeighbour"; /* 731 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $integerIntervals */ reg::packed(17, MAY_BE_ARRAY), /* __construct $floatIntervals */ @@ -102,13 +117,30 @@ inline constexpr reg::PackedArg args[] = { reg::packed(139, 0, 100), /* divide $right */ reg::packed(145, 0, 156), /* divide $phpVersion */ reg::packed(48, 0, 179), /* divide return */ + reg::packed(139, 0, 100), /* intDivide $right */ + reg::packed(145, 0, 156), /* intDivide $phpVersion */ + reg::packed(48, 0, 179), /* intDivide return */ + reg::packed(139, 0, 100), /* modulo $right */ + reg::packed(145, 0, 156), /* modulo $phpVersion */ + reg::packed(48, 0, 179), /* modulo return */ + reg::packed(139, 0, 100), /* bitwiseAnd $right */ + reg::packed(145, 0, 156), /* bitwiseAnd $phpVersion */ + reg::packed(48, 0, 179), /* bitwiseAnd return */ + reg::packed(139, 0, 100), /* bitwiseOr $right */ + reg::packed(145, 0, 156), /* bitwiseOr $phpVersion */ + reg::packed(48, 0, 179), /* bitwiseOr return */ + reg::packed(139, 0, 100), /* bitwiseXor $right */ + reg::packed(145, 0, 156), /* bitwiseXor $phpVersion */ + reg::packed(48, 0, 179), /* bitwiseXor return */ + reg::packed(145, 0, 156), /* negate $phpVersion */ + reg::packed(48, 0, 179), /* negate return */ reg::packed(139, 0, 100), /* shiftLeft $right */ reg::packed(145, 0, 156), /* shiftLeft $phpVersion */ reg::packed(48, 0, 179), /* shiftLeft return */ reg::packed(139, 0, 100), /* shiftRight $right */ reg::packed(145, 0, 156), /* shiftRight $phpVersion */ reg::packed(48, 0, 179), /* shiftRight return */ - reg::packed(247, MAY_BE_STRING), /* calculate $operator */ + reg::packed(303, MAY_BE_STRING), /* calculate $operator */ reg::packed(139, 0, 100), /* calculate $right */ reg::packed(145, 0, 156), /* calculate $phpVersion */ reg::packed(48, 0, 179), /* calculate return */ @@ -117,38 +149,53 @@ inline constexpr reg::PackedArg args[] = { reg::packed(48, MAY_BE_BOOL), /* isEmpty return */ reg::packed(48, MAY_BE_BOOL), /* hasFloatRange return */ reg::packed(48, 0, 100), /* toHull return */ - reg::packed(322, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ + reg::packed(378, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ reg::packed(48, MAY_BE_ARRAY), /* integerToFloatInterval return */ - reg::packed(322, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ + reg::packed(378, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ reg::packed(48, MAY_BE_ARRAY), /* splitIntegerDivisor return */ - reg::packed(247, MAY_BE_STRING), /* calculateIntegers $operator */ - reg::packed(374, MAY_BE_ARRAY), /* calculateIntegers $left */ + reg::packed(303, MAY_BE_STRING), /* calculateIntegers $operator */ + reg::packed(430, MAY_BE_ARRAY), /* calculateIntegers $left */ reg::packed(139, MAY_BE_ARRAY), /* calculateIntegers $right */ reg::packed(48, 0, 179), /* calculateIntegers return */ - reg::packed(397, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(403, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(453, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(459, MAY_BE_LONG), /* shiftLeftOverflows $shift */ reg::packed(48, MAY_BE_BOOL), /* shiftLeftOverflows return */ - reg::packed(374, MAY_BE_ARRAY), /* divideIntegers $left */ + reg::packed(430, MAY_BE_ARRAY), /* divideIntegers $left */ reg::packed(139, MAY_BE_ARRAY), /* divideIntegers $right */ reg::packed(145, 0, 156), /* divideIntegers $phpVersion */ reg::packed(48, MAY_BE_ARRAY), /* divideIntegers return */ - reg::packed(443, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ - reg::packed(453, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ + reg::packed(499, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ + reg::packed(509, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ reg::packed(48, MAY_BE_ARRAY), /* divideBound return */ - reg::packed(397, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(453, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ - reg::packed(247, MAY_BE_STRING), /* calculateFloats $operator */ - reg::packed(374, MAY_BE_ARRAY), /* calculateFloats $left */ + reg::packed(139, 0, 100), /* bitwiseOrXor $right */ + reg::packed(48, 0, 179), /* bitwiseOrXor return */ + reg::packed(48, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ + reg::packed(453, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(48, MAY_BE_LONG), /* allBitsMask return */ + reg::packed(48, MAY_BE_ARRAY), /* getIntegerHull return */ + reg::packed(608, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ + reg::packed(619, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ + reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude return */ + reg::packed(499, MAY_BE_ARRAY), /* intDivideIntervals $numerator */ + reg::packed(509, MAY_BE_ARRAY), /* intDivideIntervals $divisor */ + reg::packed(48, 0, 179), /* intDivideIntervals return */ + reg::packed(499, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound $numerator */ + reg::packed(509, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound $divisor */ + reg::packed(48, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound return */ + reg::packed(303, MAY_BE_STRING), /* calculateFloats $operator */ + reg::packed(430, MAY_BE_ARRAY), /* calculateFloats $left */ reg::packed(139, MAY_BE_ARRAY), /* calculateFloats $right */ reg::packed(145, 0, 156), /* calculateFloats $phpVersion */ reg::packed(48, 0, 179), /* calculateFloats return */ - reg::packed(247, MAY_BE_STRING), /* calculateFloat $operator */ - reg::packed(374, MAY_BE_DOUBLE), /* calculateFloat $left */ + reg::packed(303, MAY_BE_STRING), /* calculateFloat $operator */ + reg::packed(430, MAY_BE_DOUBLE), /* calculateFloat $left */ reg::packed(139, MAY_BE_DOUBLE), /* calculateFloat $right */ reg::packed(48, MAY_BE_DOUBLE), /* calculateFloat return */ - reg::packed(515, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ - reg::packed(521, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ - reg::packed(525, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ + reg::packed(717, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ + reg::packed(723, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ + reg::packed(727, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ reg::packed(48, MAY_BE_ARRAY), /* getFloatBoundWithInnerNeighbour return */ }; using Sig = reg::Sig; @@ -166,24 +213,37 @@ inline constexpr sigtab::Sig add = { { 197 /* add */, 2, 10, 2, 12, ZEND_ACC_PUB inline constexpr sigtab::Sig subtract = { { 201 /* subtract */, 2, 13, 2, 15, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig multiply = { { 210 /* multiply */, 2, 16, 2, 18, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig divide = { { 219 /* divide */, 2, 19, 2, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 226 /* shiftLeft */, 2, 22, 2, 24, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shiftRight = { { 236 /* shiftRight */, 2, 25, 2, 27, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig calculate = { { 256 /* calculate */, 3, 28, 3, 31, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasAnyInteger = { { 266 /* hasAnyInteger */, 0, 32, 0, 32, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig countMembers = { { 280 /* countMembers */, 0, 33, 0, 33, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isEmpty = { { 293 /* isEmpty */, 0, 34, 0, 34, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasFloatRange = { { 301 /* hasFloatRange */, 0, 35, 0, 35, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toHull = { { 315 /* toHull */, 0, 36, 0, 36, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig integerToFloatInterval = { { 331 /* integerToFloatInterval */, 1, 37, 1, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig splitIntegerDivisor = { { 354 /* splitIntegerDivisor */, 1, 39, 1, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateIntegers = { { 379 /* calculateIntegers */, 3, 41, 3, 44, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 409 /* shiftLeftOverflows */, 2, 45, 2, 47, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntegers = { { 428 /* divideIntegers */, 3, 48, 3, 51, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBound = { { 461 /* divideBound */, 2, 52, 2, 54, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntBound = { { 473 /* toIntBound */, 1, 55, 1, 56, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloats = { { 484 /* calculateFloats */, 4, 57, 4, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloat = { { 500 /* calculateFloat */, 3, 62, 3, 65, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 529 /* getFloatBoundWithInnerNeighbour */, 3, 66, 3, 69, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 226 /* intDivide */, 2, 22, 2, 24, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig modulo = { { 236 /* modulo */, 2, 25, 2, 27, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 243 /* bitwiseAnd */, 2, 28, 2, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 254 /* bitwiseOr */, 2, 31, 2, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 264 /* bitwiseXor */, 2, 34, 2, 36, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig negate = { { 275 /* negate */, 1, 37, 1, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 282 /* shiftLeft */, 2, 39, 2, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftRight = { { 292 /* shiftRight */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculate = { { 312 /* calculate */, 3, 45, 3, 48, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasAnyInteger = { { 322 /* hasAnyInteger */, 0, 49, 0, 49, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig countMembers = { { 336 /* countMembers */, 0, 50, 0, 50, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isEmpty = { { 349 /* isEmpty */, 0, 51, 0, 51, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasFloatRange = { { 357 /* hasFloatRange */, 0, 52, 0, 52, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toHull = { { 371 /* toHull */, 0, 53, 0, 53, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig integerToFloatInterval = { { 387 /* integerToFloatInterval */, 1, 54, 1, 55, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig splitIntegerDivisor = { { 410 /* splitIntegerDivisor */, 1, 56, 1, 57, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateIntegers = { { 435 /* calculateIntegers */, 3, 58, 3, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 465 /* shiftLeftOverflows */, 2, 62, 2, 64, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntegers = { { 484 /* divideIntegers */, 3, 65, 3, 68, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBound = { { 517 /* divideBound */, 2, 69, 2, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 529 /* toIntBound */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseOrXor = { { 540 /* bitwiseOrXor */, 1, 74, 1, 75, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 553 /* getNonNegativeIntegerBounds */, 0, 76, 0, 76, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig allBitsMask = { { 581 /* allBitsMask */, 1, 77, 1, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerHull = { { 593 /* getIntegerHull */, 0, 79, 0, 79, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 630 /* getMaxModuloMagnitude */, 2, 80, 2, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivideIntervals = { { 652 /* intDivideIntervals */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivideBound = { { 671 /* intDivideBound */, 2, 86, 2, 88, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloats = { { 686 /* calculateFloats */, 4, 89, 4, 93, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloat = { { 702 /* calculateFloat */, 3, 94, 3, 97, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 731 /* getFloatBoundWithInnerNeighbour */, 3, 98, 3, 101, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::NumberSet diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index aa6c878ed7f..f31ace694cc 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -41,45 +41,29 @@ inline constexpr char strings[] = "divide\0" /* 205 */ "getDivTypeFromTypes\0" /* 212 */ "modulo\0" /* 232 */ - "divisorMin\0" /* 239 */ - "divisorMax\0" /* 250 */ - "getMaxModuloMagnitude\0" /* 261 */ - "type\0" /* 283 */ - "getIntegerBounds\0" /* 288 */ - "getIntegerIntervals\0" /* 305 */ - "add\0" /* 325 */ - "arrayUnion\0" /* 329 */ - "tryArrayUnion\0" /* 340 */ - "subtract\0" /* 354 */ - "multiply\0" /* 363 */ - "shiftLeft\0" /* 372 */ - "shiftRight\0" /* 382 */ - "optimizeScalarType\0" /* 393 */ - "getNonNegativeIntegerBounds\0" /* 412 */ - "leftNumberType\0" /* 440 */ - "rightNumberType\0" /* 455 */ - "computeBitwiseAndRange\0" /* 471 */ - "computeBitwiseOrXorRange\0" /* 494 */ - "value\0" /* 519 */ - "allBitsMask\0" /* 525 */ - "operator\0" /* 537 */ - "resolveCommonMath\0" /* 546 */ - "negateNumber\0" /* 564 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 577 */ - "toIntegerOperand\0" /* 609 */ - "getTypeFromValue\0" /* 626 */ - "getNeverType\0" /* 643 */ - "negate\0" /* 656 */ - "numeratorType\0" /* 663 */ - "divisorType\0" /* 677 */ - "intDivide\0" /* 689 */ - "getIntervals\0" /* 699 */ - "numeratorMin\0" /* 712 */ - "numeratorMax\0" /* 725 */ - "divideIntervals\0" /* 738 */ - "numerator\0" /* 754 */ - "divisor\0" /* 764 */ - "divideBounds"; /* 772 */ + "type\0" /* 239 */ + "getIntegerIntervals\0" /* 244 */ + "add\0" /* 264 */ + "arrayUnion\0" /* 268 */ + "tryArrayUnion\0" /* 279 */ + "subtract\0" /* 293 */ + "multiply\0" /* 302 */ + "shiftLeft\0" /* 311 */ + "shiftRight\0" /* 321 */ + "optimizeScalarType\0" /* 332 */ + "operator\0" /* 351 */ + "resolveCommonMath\0" /* 360 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 378 */ + "toIntegerOperand\0" /* 410 */ + "value\0" /* 427 */ + "getTypeFromValue\0" /* 433 */ + "getNeverType\0" /* 450 */ + "negate\0" /* 463 */ + "numeratorType\0" /* 470 */ + "divisorType\0" /* 484 */ + "intDivide\0" /* 496 */ + "PHPStan\\Type\\NumberSet\0" /* 506 */ + "getIntegerNumberSet"; /* 529 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* concat $left */ reg::packed(23, 0, 5), /* concat $right */ @@ -117,12 +101,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 5), /* modulo $rightType */ reg::packed(29, 0, 40), /* modulo $phpVersion */ reg::packed(63, 0, 5), /* modulo return */ - 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(283, 0, 5), /* getIntegerBounds $type */ - reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerBounds return */ - reg::packed(283, 0, 5), /* getIntegerIntervals $type */ + reg::packed(239, 0, 5), /* getIntegerIntervals $type */ reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerIntervals return */ reg::packed(105, 0, 5), /* add $leftType */ reg::packed(114, 0, 5), /* add $rightType */ @@ -151,49 +130,29 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 5), /* shiftRight $rightType */ reg::packed(29, 0, 40), /* shiftRight $phpVersion */ reg::packed(63, 0, 5), /* shiftRight return */ - reg::packed(283, 0, 5), /* optimizeScalarType $type */ + reg::packed(239, 0, 5), /* optimizeScalarType $type */ reg::packed(63, 0, 5), /* optimizeScalarType return */ - reg::packed(283, 0, 5), /* getNonNegativeIntegerBounds $type */ - reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ - reg::packed(440, 0, 5), /* computeBitwiseAndRange $leftNumberType */ - reg::packed(455, 0, 5), /* computeBitwiseAndRange $rightNumberType */ - reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseAndRange return */ - reg::packed(440, 0, 5), /* computeBitwiseOrXorRange $leftNumberType */ - reg::packed(455, 0, 5), /* computeBitwiseOrXorRange $rightNumberType */ - reg::packed(63, MAY_BE_NULL, 5), /* computeBitwiseOrXorRange return */ - reg::packed(519, MAY_BE_LONG), /* allBitsMask $value */ - reg::packed(63, MAY_BE_LONG), /* allBitsMask return */ - reg::packed(537, MAY_BE_STRING), /* resolveCommonMath $operator */ + reg::packed(351, MAY_BE_STRING), /* resolveCommonMath $operator */ reg::packed(105, 0, 5), /* resolveCommonMath $leftType */ reg::packed(114, 0, 5), /* resolveCommonMath $rightType */ reg::packed(29, 0, 40), /* resolveCommonMath $phpVersion */ reg::packed(63, 0, 5), /* resolveCommonMath return */ - reg::packed(283, 0, 5), /* negateNumber $type */ - reg::packed(63, 0, 5), /* negateNumber return */ - reg::packed(283, 0, 577), /* toIntegerOperand $type */ + reg::packed(239, 0, 378), /* toIntegerOperand $type */ reg::packed(63, MAY_BE_LONG), /* toIntegerOperand return */ - reg::packed(519, 0), /* getTypeFromValue $value */ + reg::packed(427, 0), /* getTypeFromValue $value */ reg::packed(63, 0, 5), /* getTypeFromValue return */ reg::packed(105, 0, 5), /* getNeverType $leftType */ reg::packed(114, 0, 5), /* getNeverType $rightType */ reg::packed(63, 0, 5), /* getNeverType return */ - reg::packed(283, 0, 5), /* negate $type */ + reg::packed(239, 0, 5), /* negate $type */ reg::packed(29, 0, 40), /* negate $phpVersion */ reg::packed(63, 0, 5), /* negate return */ - reg::packed(663, 0, 5), /* intDivide $numeratorType */ - reg::packed(677, 0, 5), /* intDivide $divisorType */ + reg::packed(470, 0, 5), /* intDivide $numeratorType */ + reg::packed(484, 0, 5), /* intDivide $divisorType */ reg::packed(29, 0, 40), /* intDivide $phpVersion */ reg::packed(63, 0, 5), /* intDivide return */ - reg::packed(283, 0, 5), /* getIntervals $type */ - reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntervals return */ - reg::packed(712, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMin */ - reg::packed(725, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $numeratorMax */ - reg::packed(239, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMin */ - reg::packed(250, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideIntervals $divisorMax */ - reg::packed(63, 0, 5), /* divideIntervals return */ - reg::packed(754, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $numerator */ - reg::packed(764, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds $divisor */ - reg::packed(63, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBounds return */ + reg::packed(239, 0, 5), /* getIntegerNumberSet $type */ + reg::packed(63, MAY_BE_NULL, 506), /* getIntegerNumberSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -209,31 +168,22 @@ inline constexpr sigtab::Sig getFiniteOrConstantScalarTypes = { { 174 /* getFini inline constexpr sigtab::Sig divide = { { 205 /* divide */, 3, 24, 3, 27, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig getDivTypeFromTypes = { { 212 /* getDivTypeFromTypes */, 3, 28, 3, 31, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig modulo = { { 232 /* modulo */, 3, 32, 3, 35, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 261 /* getMaxModuloMagnitude */, 2, 36, 2, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerBounds = { { 288 /* getIntegerBounds */, 1, 39, 1, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerIntervals = { { 305 /* getIntegerIntervals */, 1, 41, 1, 42, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig add = { { 325 /* add */, 3, 43, 3, 46, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 329 /* arrayUnion */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 340 /* tryArrayUnion */, 2, 51, 2, 53, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig subtract = { { 354 /* subtract */, 3, 54, 3, 57, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig multiply = { { 363 /* multiply */, 3, 58, 3, 61, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 372 /* shiftLeft */, 3, 62, 3, 65, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftRight = { { 382 /* shiftRight */, 3, 66, 3, 69, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 393 /* optimizeScalarType */, 1, 70, 1, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 412 /* getNonNegativeIntegerBounds */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseAndRange = { { 471 /* computeBitwiseAndRange */, 2, 74, 2, 76, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 494 /* computeBitwiseOrXorRange */, 2, 77, 2, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig allBitsMask = { { 525 /* allBitsMask */, 1, 80, 1, 81, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 546 /* resolveCommonMath */, 4, 82, 4, 86, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negateNumber = { { 564 /* negateNumber */, 1, 87, 1, 88, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntegerOperand = { { 609 /* toIntegerOperand */, 1, 89, 1, 90, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 626 /* getTypeFromValue */, 1, 91, 1, 92, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 643 /* getNeverType */, 2, 93, 2, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 656 /* negate */, 2, 96, 2, 98, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 689 /* intDivide */, 3, 99, 3, 102, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntervals = { { 699 /* getIntervals */, 1, 103, 1, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntervals = { { 738 /* divideIntervals */, 4, 105, 4, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBounds = { { 772 /* divideBounds */, 2, 110, 2, 112, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerIntervals = { { 244 /* getIntegerIntervals */, 1, 36, 1, 37, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig add = { { 264 /* add */, 3, 38, 3, 41, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 268 /* arrayUnion */, 3, 42, 3, 45, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 279 /* tryArrayUnion */, 2, 46, 2, 48, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig subtract = { { 293 /* subtract */, 3, 49, 3, 52, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig multiply = { { 302 /* multiply */, 3, 53, 3, 56, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 311 /* shiftLeft */, 3, 57, 3, 60, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftRight = { { 321 /* shiftRight */, 3, 61, 3, 64, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig optimizeScalarType = { { 332 /* optimizeScalarType */, 1, 65, 1, 66, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolveCommonMath = { { 360 /* resolveCommonMath */, 4, 67, 4, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntegerOperand = { { 410 /* toIntegerOperand */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getTypeFromValue = { { 433 /* getTypeFromValue */, 1, 74, 1, 75, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNeverType = { { 450 /* getNeverType */, 2, 76, 2, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig negate = { { 463 /* negate */, 2, 79, 2, 81, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivide = { { 496 /* intDivide */, 3, 82, 3, 85, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerNumberSet = { { 529 /* getIntegerNumberSet */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper From e7c22f7512c4044ea50ac1c72235944634258e71 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Wed, 4 Nov 2026 22:55:06 +0000 Subject: [PATCH 04/15] Compute arithmetic and bitwise operators on ValueSet The operand handling of +, -, *, /, intdiv, %, **, negation and the bitwise operators moves from OperatorTypeHelper and ExponentiateHelper onto ValueSet and NumberSet. Types describe themselves through toValueSet() (invalid, unknown, never, arrays, benevolent) and the OperatorTypeTrait methods delegate to it. ExponentiateHelper is removed; OperatorTypeHelper keeps only concatenation and array union. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 50 +- src/Type/Accessory/AccessoryArrayListType.php | 2 +- src/Type/Accessory/NonEmptyArrayType.php | 2 +- src/Type/Accessory/OversizedArrayType.php | 2 +- src/Type/BenevolentUnionType.php | 5 + src/Type/BooleanType.php | 2 +- src/Type/CallableType.php | 5 + src/Type/ExponentiateHelper.php | 277 ---- src/Type/FloatType.php | 2 +- .../UnresolvedTemplateArgumentType.php | 6 + src/Type/IntegerType.php | 2 +- src/Type/IterableType.php | 5 + src/Type/MixedType.php | 5 + src/Type/NeverType.php | 2 +- src/Type/NonexistentParentClassType.php | 5 + src/Type/NullType.php | 2 +- src/Type/NumberSet.php | 139 +- src/Type/OperatorTypeHelper.php | 858 +----------- src/Type/StrictMixedType.php | 5 + src/Type/StringType.php | 2 +- src/Type/Traits/ArrayTypeTrait.php | 2 +- src/Type/Traits/OperatorTypeTrait.php | 47 +- src/Type/ValueSet.php | 1177 ++++++++++++++++- src/Type/VoidType.php | 5 + turbo-ext/src/AccessoryArrayListType.cpp | 2 +- turbo-ext/src/BenevolentUnionType.cpp | 7 + turbo-ext/src/BooleanType.cpp | 6 +- turbo-ext/src/CallableType.cpp | 2 + turbo-ext/src/FloatType.cpp | 6 +- turbo-ext/src/IetrSupport.h | 5 +- turbo-ext/src/IntegerType.cpp | 6 +- turbo-ext/src/IterableType.cpp | 2 + turbo-ext/src/MixedType.cpp | 2 + turbo-ext/src/NeverType.cpp | 2 +- turbo-ext/src/NonEmptyArrayType.cpp | 2 +- turbo-ext/src/NonexistentParentClassType.cpp | 2 + turbo-ext/src/NullType.cpp | 7 +- turbo-ext/src/NumberSet.cpp | 151 +++ turbo-ext/src/OperatorTypeHelper.cpp | 1042 +-------------- turbo-ext/src/OversizedArrayType.cpp | 2 +- turbo-ext/src/StrictMixedType.cpp | 2 + turbo-ext/src/StringType.cpp | 6 +- turbo-ext/src/TypeTraits.cpp | 140 +- turbo-ext/src/TypeTraits.h | 11 +- .../src/UnresolvedTemplateArgumentType.cpp | 1 + turbo-ext/src/VoidType.cpp | 2 + turbo-ext/src/generated/BenevolentUnionType.h | 6 +- turbo-ext/src/generated/CallableType.h | 6 +- turbo-ext/src/generated/IterableType.h | 6 +- turbo-ext/src/generated/MixedType.h | 6 +- .../generated/NonexistentParentClassType.h | 6 +- turbo-ext/src/generated/NumberSet.h | 206 +-- turbo-ext/src/generated/OperatorTypeHelper.h | 131 +- turbo-ext/src/generated/StrictMixedType.h | 6 +- .../UnresolvedTemplateArgumentType.h | 218 +-- turbo-ext/src/generated/VoidType.h | 6 +- turbo-ext/src/support.cpp | 1 - turbo-ext/src/support.h | 20 +- turbo-ext/tests/initializer-escape.php | 30 +- turbo-ext/tests/type-family.php | 60 +- 60 files changed, 2018 insertions(+), 2707 deletions(-) delete mode 100644 src/Type/ExponentiateHelper.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6fa1943f64c..8494de0fcef 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1015,12 +1015,6 @@ parameters: count: 2 path: src/Type/Enum/EnumCaseObjectType.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: 2 - path: src/Type/ExponentiateHelper.php - - rawMessage: Doing instanceof PHPStan\Type\Generic\GenericObjectType is error-prone and deprecated. identifier: phpstanApi.instanceofType @@ -1459,52 +1453,10 @@ 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 + count: 4 path: src/Type/OperatorTypeHelper.php - diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index e2de4196c38..5bb4ceb6bb8 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -596,7 +596,7 @@ public function hasTemplateOrLateResolvableType(): bool public function toValueSet(): ValueSet { - return ValueSet::createArrays(true, true); + return ValueSet::createArrays($this); } } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index b1d0e5297b2..0ed7c192c61 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -575,7 +575,7 @@ public function hasTemplateOrLateResolvableType(): bool public function toValueSet(): ValueSet { - return ValueSet::createArrays(false, true); + return ValueSet::createArrays($this); } } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index b65f8dab9ef..b8173e23d11 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -545,7 +545,7 @@ public function hasTemplateOrLateResolvableType(): bool public function toValueSet(): ValueSet { - return ValueSet::createArrays(false, true); + return ValueSet::createArrays($this); } } diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index 5a44af5e202..df21b4a2d6b 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -182,4 +182,9 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return TypeUtils::toBenevolentUnion($newType); } + public function toValueSet(): ValueSet + { + return parent::toValueSet()->toBenevolent(); + } + } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 828159ac8c3..82ae08db0cb 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -184,7 +184,7 @@ public function getFiniteTypes(): array public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return ExponentiateHelper::exponentiate($this, $exponent); + return $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); } public function toPhpDocNode(): TypeNode diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 6bb9f55b60e..590cfb0f0e7 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -882,4 +882,9 @@ public function hasTemplateOrLateResolvableType(): bool return $this->getReturnType()->hasTemplateOrLateResolvableType(); } + public function toValueSet(): ValueSet + { + return ValueSet::createInvalid(); + } + } diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php deleted file mode 100644 index eca68df2712..00000000000 --- a/src/Type/ExponentiateHelper.php +++ /dev/null @@ -1,277 +0,0 @@ -getTypes() as $unionType) { - $results[] = self::exponentiate($base, $unionType); - } - return TypeCombinator::union(...$results); - } - - if ($exponent instanceof NeverType) { - return new NeverType(); - } - - $allowedExponentTypes = new UnionType([ - new IntegerType(), - new FloatType(), - new StringType(), - new BooleanType(), - new NullType(), - ]); - if (!$allowedExponentTypes->isSuperTypeOf($exponent)->yes()) { - 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(); - } - } - - $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; - } - } - - // 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 ($isFloatResult) { - return new ConstantFloatType(1.0); - } - if ($base->toNumber()->isInteger()->yes()) { - return new ConstantIntegerType(1); - } - - return new UnionType([new ConstantIntegerType(1), new ConstantFloatType(1.0)]); - } - - if ($isFloatResult) { - return new FloatType(); - } - - $isLooseOne = (new ConstantIntegerType(1))->isSuperTypeOf($exponentNumberType); - if ($isLooseOne->yes()) { - $possibleResults = new UnionType([ - new FloatType(), - new IntegerType(), - ]); - - if ($possibleResults->isSuperTypeOf($base)->yes()) { - return $base; - } - } - - return new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - ]); - } - - private static function exponentiateConstantScalar(ConstantScalarType $base, Type $exponent): ?Type - { - if (!$exponent instanceof ConstantScalarType) { - return null; - } - - $baseValue = $base->getValue(); - $result = self::pow($baseValue, $exponent->getValue()); - if ($result === null) { - return new ErrorType(); - } - - $resultType = is_int($result) ? new ConstantIntegerType($result) : new ConstantFloatType($result); - - // a zero float stands for both zeros (0.0 === -0.0), and a negative odd exponent tells them apart - if (is_float($baseValue) && $baseValue === 0.0) { - $otherZeroResult = self::pow(-$baseValue, $exponent->getValue()); - if (is_float($otherZeroResult) && $otherZeroResult !== $result) { - return TypeCombinator::union($resultType, new ConstantFloatType($otherZeroResult)); - } - } - - return $resultType; - } - - /** - * @return array{int|null, int|null}|null - */ - private static function getIntegerBounds(Type $type): ?array - { - $numberSet = $type->toNumberSet(); - if ($numberSet === null || $numberSet->getFloatIntervals() !== [] || $numberSet->hasNan()) { - return null; - } - - // plain int stays (float|int): the intervals read an open end as no bound - // at all, not as PHP_INT_MIN or PHP_INT_MAX, whose powers overflow - $intervals = $numberSet->getIntegerIntervals(); - if ($intervals === [[null, null]] || count($intervals) !== 1) { - return null; - } - - return $intervals[0]; - } - - /** - * 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 null; - } - } - - 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; - } - - $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 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 ($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; - } - $minBase = 0; - $maxBase = $baseMin === null || $baseMax === null ? null : max(-$baseMin, $baseMax); - } - - $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 - { - if (is_string($base) && !is_numeric($base)) { - return null; - } - if (is_string($exp) && !is_numeric($exp)) { - return null; - } - // PHP 8.4 deprecates 0 ** -1, which still gives INF - return @pow($base, $exp); - } - -} diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 40cbabf3f63..9df3e6232b0 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -338,7 +338,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return ExponentiateHelper::exponentiate($this, $exponent); + return $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); } public function toPhpDocNode(): TypeNode diff --git a/src/Type/Generic/UnresolvedTemplateArgumentType.php b/src/Type/Generic/UnresolvedTemplateArgumentType.php index b69c0b3a2ac..3f4c130dcbf 100644 --- a/src/Type/Generic/UnresolvedTemplateArgumentType.php +++ b/src/Type/Generic/UnresolvedTemplateArgumentType.php @@ -27,6 +27,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use PHPStan\Type\VerbosityLevel; use function spl_object_id; use function sprintf; @@ -691,6 +692,11 @@ public function toNumber(): Type return $this->getDelegate()->toNumber(); } + public function toValueSet(): ValueSet + { + return $this->getDelegate()->toValueSet(); + } + public function toBitwiseNotType(): Type { return $this->getDelegate()->toBitwiseNotType(); diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index b1a6cb7ed2b..97c4f08d09f 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -203,7 +203,7 @@ public function getFiniteTypes(): array public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return ExponentiateHelper::exponentiate($this, $exponent); + return $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); } public function toPhpDocNode(): TypeNode diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 1f2b484b25a..18b9608f9e2 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -566,4 +566,9 @@ public function hasTemplateOrLateResolvableType(): bool return $this->keyType->hasTemplateOrLateResolvableType() || $this->itemType->hasTemplateOrLateResolvableType(); } + public function toValueSet(): ValueSet + { + return ValueSet::createInvalid(); + } + } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 8186bf4f6a6..18846e04ac7 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -1255,4 +1255,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createUnknown($this); + } + } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 8ce79def876..24b715105af 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -677,7 +677,7 @@ public function hasTemplateOrLateResolvableType(): bool public function toValueSet(): ValueSet { - return ValueSet::createEmpty(); + return ValueSet::createNever($this); } } diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 9708b9d72bf..341cda93003 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -287,4 +287,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createInvalid(); + } + } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 104b3ae3b95..ec3dd736274 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -420,7 +420,7 @@ public function getFiniteTypes(): array public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return ExponentiateHelper::exponentiate($this, $exponent); + return $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); } public function toPhpDocNode(): TypeNode diff --git a/src/Type/NumberSet.php b/src/Type/NumberSet.php index 799874e56d3..15cac0a9686 100644 --- a/src/Type/NumberSet.php +++ b/src/Type/NumberSet.php @@ -271,6 +271,23 @@ public function negate(PhpVersion $phpVersion): Type return TypeCombinator::union(...$types); } + /** + * Models the ** operator on integers: the integers $base ** $exponent gives for one + * integer interval each, with a non-negative exponent. Null when that is not an integer + * interval PHP computes without overflowing into a float, or for other numbers; plain + * int counts as no bound at all, not PHP_INT_MIN or PHP_INT_MAX, whose powers overflow. + */ + public function power(self $exponent, PhpVersion $phpVersion): ?Type + { + $baseBounds = $this->getBoundedIntegerInterval(); + $exponentBounds = $exponent->getBoundedIntegerInterval(); + if ($baseBounds === null || $exponentBounds === null) { + return null; + } + + return self::powerIntegerIntervals($baseBounds[0], $baseBounds[1], $exponentBounds[0], $exponentBounds[1]); + } + /** Models the << operator; a negative shift is an error. */ public function shiftLeft(self $right, PhpVersion $phpVersion): Type { @@ -386,7 +403,7 @@ private function countMembers(): int return count($this->integerIntervals) + count($this->floatIntervals) + ($this->nan ? 1 : 0); } - private function isEmpty(): bool + public function isEmpty(): bool { return $this->integerIntervals === [] && $this->floatIntervals === [] && !$this->nan; } @@ -546,6 +563,126 @@ private static function calculateIntegers(string $operator, array $left, array $ return IntegerRangeType::fromInterval(is_int($min) ? $min : null, is_int($max) ? $max : null); } + /** + * The one integer interval of a set of integers, unless it is plain int. + * + * @return array{int|null, int|null}|null + */ + private function getBoundedIntegerInterval(): ?array + { + if ($this->floatIntervals !== [] || $this->nan || count($this->integerIntervals) !== 1 || $this->integerIntervals[0] === [null, null]) { + return null; + } + + return $this->integerIntervals[0]; + } + + /** + * 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 powerIntegerIntervals(?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 null; + } + } + + 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; + } + + $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::powerIntegerInterval($baseMin, $baseMax, $exponentMin + $i); + if ($result === null) { + 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 powerIntegerInterval(?int $baseMin, ?int $baseMax, int $exponent): ?Type + { + if ($exponent === 0) { + return new ConstantIntegerType(1); + } + + 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; + } + $minBase = 0; + $maxBase = $baseMin === null || $baseMax === null ? null : max(-$baseMin, $baseMax); + } + + $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; + } + /** * 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. diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index 02307452994..ff1edb3948c 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -5,7 +5,6 @@ use Nette\Utils\Strings; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; @@ -17,25 +16,19 @@ use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use function array_merge; use function count; -use function in_array; -use function is_int; use function is_numeric; /** - * The operators of Type::arithmetic*(), Type::bitwise*(), Type::stringConcatenate() and - * Type::arrayUnion() for the types that do not decide the result on their own. + * The operators of Type::stringConcatenate() and Type::arrayUnion() for the types that do not + * decide the result on their own. The arithmetic and bitwise operators are on ValueSet. */ #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/OperatorTypeHelper.cpp')] final class OperatorTypeHelper { - private const IS_SCALAR_TYPE = 1; - private const IS_UNKNOWN = 2; - public static function concat(Type $left, Type $right, PhpVersion $phpVersion): Type { $leftStringType = $left->toString(); @@ -181,472 +174,6 @@ private static function canEndInWhitespace(Type $left, Type $leftStringType, Php return $phpVersion->allowsTrailingWhitespaceInNumericStrings(); } - 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) - ) { - // 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(); - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $anyInteger = new NumberSet([[null, null]], [], false); - - return ($leftNumberType->toNumberSet() ?? $anyInteger)->bitwiseAnd($rightNumberType->toNumberSet() ?? $anyInteger, $phpVersion); - } - - 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) - ) { - // 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(); - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $anyInteger = new NumberSet([[null, null]], [], false); - - return ($leftNumberType->toNumberSet() ?? $anyInteger)->bitwiseOr($rightNumberType->toNumberSet() ?? $anyInteger, $phpVersion); - } - - 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) - ) { - // 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(); - } - - if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { - return new ErrorType(); - } - - $anyInteger = new NumberSet([[null, null]], [], false); - - return ($leftNumberType->toNumberSet() ?? $anyInteger)->bitwiseXor($rightNumberType->toNumberSet() ?? $anyInteger, $phpVersion); - } - - /** - * @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(self::toIntegerOperand($leftTypeInner), self::toIntegerOperand($rightTypeInner)); - $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, $phpVersion); - - 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, 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(); - } - - 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, $phpVersion); - } - - 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 = self::toIntegerOperand($rightTypeInner); - if ($rightIntegerValue === 0) { - return new ErrorType(); - } - - $resultType = self::getTypeFromValue(self::toIntegerOperand($leftTypeInner) % $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(); - } - } - - $anyInteger = new NumberSet([[null, null]], [], false); - $leftNumbers = self::getIntegerNumberSet($leftType->toInteger()) ?? $anyInteger; - $rightNumbers = self::getIntegerNumberSet($rightType->toInteger()) ?? $anyInteger; - - return $leftNumbers->modulo($rightNumbers, $phpVersion); - } - - /** - * @return non-empty-list|null the integer intervals of a type - * that holds integers and nothing else - */ - private static function getIntegerIntervals(Type $type): ?array - { - $numberSet = $type->toNumberSet(); - if ($numberSet === null || $numberSet->getFloatIntervals() !== [] || $numberSet->hasNan()) { - return null; - } - - $intervals = $numberSet->getIntegerIntervals(); - if ($intervals === []) { - return null; - } - - return $intervals; - } - - public static function add(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type - { - 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, $phpVersion); - } - public static function arrayUnion(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { return self::tryArrayUnion($leftType, $rightType) ?? new ErrorType(); @@ -790,385 +317,4 @@ private static function tryArrayUnion(Type $leftType, Type $rightType): ?Type 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, $phpVersion); - } - - 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); - } - - // zero times an integer is always 0; times a float it is 0.0, -0.0 or NAN - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - if ( - ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0 && $rightNumberType->isInteger()->yes()) - || ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0 && $leftNumberType->isInteger()->yes()) - ) { - return new ConstantIntegerType(0); - } - - return self::resolveCommonMath('*', $leftType, $rightType, $phpVersion); - } - - 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(); - } - - $rightIntegerValue = self::toIntegerOperand($rightTypeInner); - if ($rightIntegerValue < 0) { - return new ErrorType(); - } - - $resultType = self::getTypeFromValue(self::toIntegerOperand($leftTypeInner) << $rightIntegerValue); - $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, $phpVersion); - } - - 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(); - } - - $rightIntegerValue = self::toIntegerOperand($rightTypeInner); - if ($rightIntegerValue < 0) { - return new ErrorType(); - } - - $resultType = self::getTypeFromValue(self::toIntegerOperand($leftTypeInner) >> $rightIntegerValue); - $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, $phpVersion); - } - - 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); - } - - /** - * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator - */ - /** - * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator - */ - private static function resolveCommonMath(string $operator, Type $leftType, Type $rightType, PhpVersion $phpVersion): Type - { - $types = TypeCombinator::union($leftType, $rightType); - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if (!$types instanceof MixedType) { - $leftNumberSet = $leftNumberType->toNumberSet(); - $rightNumberSet = $rightNumberType->toNumberSet(); - if ($leftNumberSet !== null && $rightNumberSet !== null) { - if ($operator === '+') { - $result = $leftNumberSet->add($rightNumberSet, $phpVersion); - } elseif ($operator === '-') { - $result = $leftNumberSet->subtract($rightNumberSet, $phpVersion); - } elseif ($operator === '*') { - $result = $leftNumberSet->multiply($rightNumberSet, $phpVersion); - } elseif ($operator === '/') { - $result = $leftNumberSet->divide($rightNumberSet, $phpVersion); - } elseif ($operator === '<<') { - $result = $leftNumberSet->shiftLeft($rightNumberSet, $phpVersion); - } else { - $result = $leftNumberSet->shiftRight($rightNumberSet, $phpVersion); - } - - if ( - $leftNumberType instanceof BenevolentUnionType - || $rightNumberType instanceof BenevolentUnionType - || $leftType instanceof BenevolentUnionType - || $rightType instanceof BenevolentUnionType - ) { - return TypeUtils::toBenevolentUnion($result); - } - - return $result; - } - } - - 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; - } - - /** - * 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 - */ - 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 - { - $number = $type->toNumber(); - $numberSet = $number->toNumberSet(); - if ($numberSet === null) { - return $number; - } - - $negated = $numberSet->negate($phpVersion); - if ($number instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($negated); - } - - return $negated; - } - - public static function intDivide(Type $numeratorType, Type $divisorType, PhpVersion $phpVersion): Type - { - $numerator = self::getIntegerNumberSet($numeratorType->toInteger()); - $divisor = self::getIntegerNumberSet($divisorType->toInteger()); - if ($numerator === null || $divisor === null) { - return new ErrorType(); - } - - return $numerator->intDivide($divisor, $phpVersion); - } - - /** The numbers of a type that holds integers and nothing else, or null. */ - private static function getIntegerNumberSet(Type $type): ?NumberSet - { - $intervals = self::getIntegerIntervals($type); - if ($intervals === null) { - return $type->isInteger()->yes() ? new NumberSet([[null, null]], [], false) : null; - } - - return new NumberSet($intervals, [], false); - } - } diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 017e284c7d3..3ec18075ece 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -535,4 +535,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createInvalid(); + } + } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 03a76d20b38..75112048570 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -347,7 +347,7 @@ public function getFiniteTypes(): array public function arithmeticPower(Type $exponent, PhpVersion $phpVersion): Type { - return ExponentiateHelper::exponentiate($this, $exponent); + return $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); } public function toPhpDocNode(): TypeNode diff --git a/src/Type/Traits/ArrayTypeTrait.php b/src/Type/Traits/ArrayTypeTrait.php index 23465816710..cb1aff7f754 100644 --- a/src/Type/Traits/ArrayTypeTrait.php +++ b/src/Type/Traits/ArrayTypeTrait.php @@ -225,7 +225,7 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type public function toValueSet(): ValueSet { - return ValueSet::createArrays(!$this->isIterableAtLeastOnce()->yes(), !$this->isIterableAtLeastOnce()->no()); + return ValueSet::createArrays($this); } } diff --git a/src/Type/Traits/OperatorTypeTrait.php b/src/Type/Traits/OperatorTypeTrait.php index 996b3fed129..34aaa56d227 100644 --- a/src/Type/Traits/OperatorTypeTrait.php +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -7,6 +7,7 @@ use PHPStan\Type\NumberSet; use PHPStan\Type\OperatorTypeHelper; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\ValueSet; trait OperatorTypeTrait @@ -29,62 +30,84 @@ public function toValueSet(): ValueSet public function arithmeticAdd(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::add($this, $right, $phpVersion); + $leftValues = $this->toValueSet(); + $rightValues = $right->toValueSet(); + if (!$leftValues->isEmpty() && !$rightValues->isEmpty() && $this->isArray()->yes() && $right->isArray()->yes()) { + return $this->arrayUnion($right, $phpVersion); + } + + // strings, numbers, booleans and arrays added to each other give each of them, + // as the operand types themselves to keep what else they tell + if ($this->isArray()->maybe() && $right->isArray()->maybe()) { + $leftIsPlusable = $leftValues->isPlusable(); + $rightIsPlusable = $rightValues->isPlusable(); + if ($leftIsPlusable && $rightIsPlusable) { + return TypeCombinator::union($this, $right); + } + if ($leftIsPlusable && $rightValues->isUnknown()) { + return $this; + } + if ($rightIsPlusable && $leftValues->isUnknown()) { + return $right; + } + } + + return $leftValues->arithmeticAdd($rightValues, $phpVersion); } public function arithmeticSubtract(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::subtract($this, $right, $phpVersion); + return $this->toValueSet()->arithmeticSubtract($right->toValueSet(), $phpVersion); } public function arithmeticMultiply(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::multiply($this, $right, $phpVersion); + return $this->toValueSet()->arithmeticMultiply($right->toValueSet(), $phpVersion); } public function arithmeticDivide(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::divide($this, $right, $phpVersion); + return $this->toValueSet()->arithmeticDivide($right->toValueSet(), $phpVersion); } public function arithmeticIntDivide(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::intDivide($this, $right, $phpVersion); + return $this->toInteger()->toValueSet()->arithmeticIntDivide($right->toInteger()->toValueSet(), $phpVersion); } public function arithmeticModulo(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::modulo($this, $right, $phpVersion); + return $this->toValueSet()->arithmeticModulo($right->toValueSet(), $phpVersion); } public function arithmeticNegate(PhpVersion $phpVersion): Type { - return OperatorTypeHelper::negate($this, $phpVersion); + return $this->toValueSet()->arithmeticNegate($phpVersion); } public function bitwiseAnd(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::bitwiseAnd($this, $right, $phpVersion); + return $this->toValueSet()->bitwiseAnd($right->toValueSet(), $phpVersion); } public function bitwiseOr(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::bitwiseOr($this, $right, $phpVersion); + return $this->toValueSet()->bitwiseOr($right->toValueSet(), $phpVersion); } public function bitwiseXor(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::bitwiseXor($this, $right, $phpVersion); + return $this->toValueSet()->bitwiseXor($right->toValueSet(), $phpVersion); } public function bitwiseShiftLeft(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::shiftLeft($this, $right, $phpVersion); + return $this->toValueSet()->bitwiseShiftLeft($right->toValueSet(), $phpVersion); } public function bitwiseShiftRight(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::shiftRight($this, $right, $phpVersion); + return $this->toValueSet()->bitwiseShiftRight($right->toValueSet(), $phpVersion); } public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php index d3f71505890..9a76b4d3fb7 100644 --- a/src/Type/ValueSet.php +++ b/src/Type/ValueSet.php @@ -3,11 +3,14 @@ namespace PHPStan\Type; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\TrinaryLogic; use PHPStan\Turbo\ReferencedByTurboExtension; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use function abs; @@ -20,7 +23,9 @@ use function is_infinite; use function is_int; use function is_nan; +use function is_numeric; use function is_string; +use function pow; use function sprintf; use const INF; use const NAN; @@ -61,15 +66,33 @@ private function __construct( private ?Type $strings = null, private bool $emptyArray = false, private bool $nonEmptyArray = false, + private ?Type $arrays = null, private ?Type $objects = null, private bool $resources = false, + private bool $invalid = false, + private bool $benevolent = false, + private ?NeverType $never = null, + private ?Type $unknownType = null, ) { } - public static function createUnknown(): self + /** Any value; a mixed type can tell some values apart, such as mixed~array. */ + public static function createUnknown(?MixedType $type = null): self { - return new self(true); + return new self(true, unknownType: $type); + } + + /** Whether the values are only strings, numbers, booleans and arrays, which + adds to each other. */ + public function isPlusable(): bool + { + return !$this->unknown && !$this->null && $this->objects === null && !$this->resources && !$this->isEmpty(); + } + + /** Whether the values can be anything, as mixed does. */ + public function isUnknown(): bool + { + return $this->unknown && !$this->invalid; } public static function createEmpty(): self @@ -77,6 +100,18 @@ public static function createEmpty(): self return new self(false); } + /** The values of a type no operator accepts, such as void or strict mixed; it compares as an unknown value. */ + public static function createInvalid(): self + { + return new self(true, invalid: true); + } + + /** No values, the operators give the never type. */ + public static function createNever(NeverType $never): self + { + return new self(false, never: $never); + } + public static function createNull(): self { return new self(false, null: true); @@ -103,9 +138,12 @@ public static function createStrings(Type $strings): self return new self(false, strings: $strings); } - public static function createArrays(bool $empty, bool $nonEmpty): self + /** Arrays of an array type: ArrayType, ConstantArrayType or an accessory such as NonEmptyArrayType. */ + public static function createArrays(Type $arrays): self { - return new self(false, emptyArray: $empty, nonEmptyArray: $nonEmpty); + $isIterableAtLeastOnce = $arrays->isIterableAtLeastOnce(); + + return new self(false, emptyArray: !$isIterableAtLeastOnce->yes(), nonEmptyArray: !$isIterableAtLeastOnce->no(), arrays: $arrays); } public static function createObjects(Type $objects): self @@ -118,10 +156,41 @@ public static function createResources(): self return new self(false, resources: true); } + /** The same values; the results of the operators on them are benevolent. */ + public function toBenevolent(): self + { + $valueSet = clone $this; + $valueSet->benevolent = true; + + return $valueSet; + } + + public function isEmpty(): bool + { + return !$this->unknown + && !$this->null + && !$this->false + && !$this->true + && ($this->numbers === null || $this->numbers->isEmpty()) + && $this->constantStrings === [] + && $this->strings === null + && !$this->emptyArray + && !$this->nonEmptyArray + && $this->objects === null + && !$this->resources; + } + public function union(self $other): self { if ($this->unknown || $other->unknown) { - return self::createUnknown(); + return new self( + true, + invalid: $this->invalid || $other->invalid, + benevolent: $this->benevolent || $other->benevolent, + unknownType: $this->unknown && $other->unknown && $this->unknownType !== null && $other->unknownType !== null + ? TypeCombinator::union($this->unknownType, $other->unknownType) + : null, + ); } $constantStrings = $this->constantStrings; @@ -143,8 +212,11 @@ public function union(self $other): self self::unionTypes($this->strings, $other->strings), $this->emptyArray || $other->emptyArray, $this->nonEmptyArray || $other->nonEmptyArray, + self::unionTypes($this->arrays, $other->arrays), self::unionTypes($this->objects, $other->objects), $this->resources || $other->resources, + benevolent: $this->benevolent || $other->benevolent, + never: $this->never ?? $other->never, ); } @@ -184,8 +256,11 @@ public function intersect(self $other): self $this->strings === null || $other->strings === null ? null : TypeCombinator::intersect($this->strings, $other->strings), $this->emptyArray && $other->emptyArray, $this->nonEmptyArray && $other->nonEmptyArray, + $this->arrays === null || $other->arrays === null ? null : TypeCombinator::intersect($this->arrays, $other->arrays), $this->objects === null || $other->objects === null ? null : TypeCombinator::intersect($this->objects, $other->objects), $this->resources && $other->resources, + benevolent: $this->benevolent || $other->benevolent, + never: $this->never ?? $other->never, ); } @@ -291,6 +366,1098 @@ public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type return $alwaysEqualType ?? new NeverType(); } + /** + * Models the + operator on all but two arrays, which OperatorTypeTrait::arithmeticAdd() + * unions. An array and a value that is not one cannot be added. + */ + public function arithmeticAdd(self $right, PhpVersion $phpVersion): Type + { + $result = $this->getResultOfOperands($right) ?? $this->fold('+', $right); + if ($result !== null) { + return $result; + } + + [$left, $right] = $this->generalizeTooManyConstants($right); + if ($left !== $this) { + return $left->arithmeticAdd($right, $phpVersion); + } + + $leftIsArray = $this->isArray(); + $rightIsArray = $right->isArray(); + if ($this->unknown && $right->unknown) { + 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 ($this->isOnlyNonEmptyArrays() || $right->isOnlyNonEmptyArrays()) { + return TypeCombinator::intersect($resultType, new NonEmptyArrayType()); + } + + return $resultType; + } + return $this->calculateCommonMath('+', $right, $phpVersion); + } + + /** Models the - operator. */ + public function arithmeticSubtract(self $right, PhpVersion $phpVersion): Type + { + $result = $this->invalidOrFold('-', $right); + if ($result !== null) { + return $result; + } + + [$left, $right] = $this->generalizeTooManyConstants($right); + + return $left->calculateCommonMath('-', $right, $phpVersion); + } + + /** Models the * operator. */ + public function arithmeticMultiply(self $right, PhpVersion $phpVersion): Type + { + $result = $this->invalidOrFold('*', $right); + if ($result !== null) { + return $result; + } + + [$left, $right] = $this->generalizeTooManyConstants($right); + + return $left->calculateCommonMath('*', $right, $phpVersion); + } + + /** + * Models the / operator. A divisor that is zero as a constant is an error; the quotient of + * integers that divide without a remainder is an integer. + */ + public function arithmeticDivide(self $right, PhpVersion $phpVersion): Type + { + $result = $this->invalidOrFold('/', $right); + if ($result === null) { + [$left, $generalizedRight] = $this->generalizeTooManyConstants($right); + $result = $generalizedRight->getZeroDivisorError() ?? $left->calculateCommonMath('/', $generalizedRight, $phpVersion); + } + if (!$this->isOnlyIntegers() || !$right->isOnlyIntegers()) { + return $result; + } + + $moduloType = $this->arithmeticModulo($right, $phpVersion); + if (!$moduloType->isInteger()->yes() || !(new ConstantIntegerType(0))->isSuperTypeOf($moduloType)->yes()) { + return $result; + } + + // PHP_INT_MIN / -1 divides without a remainder but still overflows to a float + $withoutFloat = TypeCombinator::remove($result, new FloatType()); + + return $withoutFloat instanceof NeverType ? $result : $withoutFloat; + } + + /** Models intdiv() of the sets of Type::toInteger() of the operands. */ + public function arithmeticIntDivide(self $right, PhpVersion $phpVersion): Type + { + $leftNumbers = $this->getIntegerNumbers(); + $rightNumbers = $right->getIntegerNumbers(); + if ($leftNumbers === null || $rightNumbers === null) { + return new ErrorType(); + } + + return $leftNumbers->intDivide($rightNumbers, $phpVersion); + } + + /** The numbers of a set of integers and nothing else, or null. */ + private function getIntegerNumbers(): ?NumberSet + { + if ( + $this->numbers === null + || $this->numbers->getIntegerIntervals() === [] + || $this->numbers->getFloatIntervals() !== [] + || $this->numbers->hasNan() + || $this->mayBeNonNumber() + ) { + return null; + } + + return $this->numbers; + } + + /** Models the % operator: the operands are converted to integers, a zero divisor is an error. */ + public function arithmeticModulo(self $right, PhpVersion $phpVersion): Type + { + $result = $this->getResultOfOperands($right) ?? $this->fold('%', $right); + if ($result !== null) { + return $result; + } + + [$left, $right] = $this->generalizeTooManyConstants($right); + if ($left !== $this) { + return $left->arithmeticModulo($right, $phpVersion); + } + + $leftNumbers = $this->toOperandNumbers(false, true); + $rightNumbers = $right->toOperandNumbers(false, true); + if ($leftNumbers === null || $rightNumbers === null) { + return new ErrorType(); + } + + $zeroDivisorError = $right->getZeroDivisorError(); + if ($zeroDivisorError !== null) { + return $zeroDivisorError; + } + + return $this->toResult($right, $leftNumbers->modulo($rightNumbers, $phpVersion)); + } + + /** + * Models the ** operator with a base of null, booleans, numbers and strings; the + * other types raise to a power on their own. The members of the exponent are raised + * to one at a time. + */ + public function arithmeticPower(self $exponent, PhpVersion $phpVersion): Type + { + if ($exponent->isEmpty()) { + return new NeverType(); + } + if ( + $exponent->unknown + || $exponent->emptyArray + || $exponent->nonEmptyArray + || $exponent->objects !== null + || $exponent->resources + ) { + return new ErrorType(); + } + + $resultTypes = []; + foreach ($exponent->getMembers() as $member) { + $resultTypes[] = $this->powerMember($member, $phpVersion); + } + + return TypeCombinator::union(...$resultTypes); + } + + private function powerMember(self $exponent, PhpVersion $phpVersion): Type + { + foreach ($exponent->constantStrings as $constantString) { + if (!is_numeric($constantString)) { + return new ErrorType(); + } + } + + $baseTypes = $this->getFiniteScalarTypes(false); + if ($baseTypes !== null && count($baseTypes) === 1) { + $exponentTypes = $exponent->getFiniteScalarTypes(false); + if ($exponentTypes !== null && count($exponentTypes) === 1) { + return self::foldPower($baseTypes[0]->getValue(), $exponentTypes[0]->getValue()) ?? new ErrorType(); + } + + $baseValue = $baseTypes[0]->getValue(); + if (is_string($baseValue) && !is_numeric($baseValue)) { + return new ErrorType(); + } + } + + $baseNumbers = $this->toNumberSetOfNumber(); + $exponentNumbers = $exponent->toNumberSetOfNumber(); + if ($baseNumbers !== null && $exponentNumbers !== null) { + $result = $baseNumbers->power($exponentNumbers, $phpVersion); + if ($result !== null) { + return $result; + } + } + + // a float operand makes the result a float + $isFloatResult = $this->isOnlyFloats($this->numbers) || ($exponentNumbers !== null && self::isOnlyFloatNumbers($exponentNumbers)); + if ($exponentNumbers !== null && self::isOnlyInteger($exponentNumbers, 0)) { + if ($isFloatResult) { + return new ConstantFloatType(1.0); + } + if ($baseNumbers !== null && $baseNumbers->getIntegerIntervals() !== [] && $baseNumbers->getFloatIntervals() === [] && !$baseNumbers->hasNan()) { + return new ConstantIntegerType(1); + } + + return new UnionType([new ConstantIntegerType(1), new ConstantFloatType(1.0)]); + } + + if ($isFloatResult) { + return new FloatType(); + } + + if ($exponentNumbers !== null && self::isOnlyInteger($exponentNumbers, 1) && $this->numbers !== null && !$this->mayBeNonNumber()) { + return self::createTypeOfNumbers($this->numbers); + } + + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + ]); + } + + /** + * The values one at a time, the way the members of a union type are: each kind of + * value, each number interval, each constant string. The booleans and a float with + * NAN stay together, as BooleanType and FloatType are one type. + * + * @return list + */ + private function getMembers(): array + { + $members = []; + if ($this->null) { + $members[] = self::createNull(); + } + if ($this->false || $this->true) { + $members[] = self::createBooleans($this->false, $this->true); + } + if ($this->numbers !== null) { + $nan = $this->numbers->hasNan(); + foreach ($this->numbers->getIntegerIntervals() as $interval) { + $members[] = self::createNumbers(new NumberSet([$interval], [], false)); + } + foreach ($this->numbers->getFloatIntervals() as $interval) { + $withNan = $nan && $interval === [-INF, INF]; + $nan = $nan && !$withNan; + $members[] = self::createNumbers(new NumberSet([], [$interval], $withNan)); + } + if ($nan) { + $members[] = self::createNumbers(new NumberSet([], [], true)); + } + } + foreach ($this->constantStrings as $constantString) { + $members[] = self::createConstantString($constantString); + } + if ($this->strings !== null) { + $members[] = self::createStrings($this->strings); + } + + return $members; + } + + /** The numbers Type::toNumber() gives for the values, null when that is not only numbers. */ + private function toNumberSetOfNumber(): ?NumberSet + { + $integerIntervals = []; + if ($this->null || $this->false) { + $integerIntervals[] = [0, 0]; + } + if ($this->true) { + $integerIntervals[] = [1, 1]; + } + $numbers = new NumberSet($integerIntervals, [], false); + if ($this->numbers !== null) { + $numbers = $numbers->union($this->numbers); + } + foreach ($this->constantStrings as $constantString) { + $stringNumbers = (new ConstantStringType($constantString))->toNumber()->toNumberSet(); + if ($stringNumbers === null) { + return null; + } + $numbers = $numbers->union($stringNumbers); + } + if ($this->strings !== null) { + $stringNumbers = $this->strings->toNumber()->toNumberSet(); + if ($stringNumbers === null) { + return null; + } + $numbers = $numbers->union($stringNumbers); + } + + return $numbers; + } + + private function isOnlyFloats(?NumberSet $numbers): bool + { + return $numbers !== null && !$this->mayBeNonNumber() && self::isOnlyFloatNumbers($numbers); + } + + private static function isOnlyFloatNumbers(NumberSet $numbers): bool + { + return $numbers->getIntegerIntervals() === [] && ($numbers->getFloatIntervals() !== [] || $numbers->hasNan()); + } + + private static function isOnlyInteger(NumberSet $numbers, int $value): bool + { + return $numbers->getIntegerIntervals() === [[$value, $value]] && $numbers->getFloatIntervals() === [] && !$numbers->hasNan(); + } + + private static function createTypeOfNumbers(NumberSet $numbers): Type + { + $types = []; + foreach ($numbers->getIntegerIntervals() as [$min, $max]) { + $types[] = IntegerRangeType::fromInterval($min, $max); + } + $nan = $numbers->hasNan(); + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + if ($nan && $min === -INF && $max === INF) { + $types[] = new FloatType(); + $nan = false; + continue; + } + + $types[] = $min === $max ? new ConstantFloatType($min) : FloatRangeType::fromInterval($min, $max); + } + if ($nan) { + $types[] = new ConstantFloatType(NAN); + } + + return TypeCombinator::union(...$types); + } + + /** Models the unary - operator. */ + public function arithmeticNegate(PhpVersion $phpVersion): Type + { + if ($this->isEmpty()) { + return $this->never ?? new NeverType(); + } + + $numbers = $this->invalid ? null : $this->toOperandNumbers(false, false); + if ($numbers === null) { + return new ErrorType(); + } + + $negated = $numbers->negate($phpVersion); + + return $this->benevolent ? TypeUtils::toBenevolentUnion($negated) : $negated; + } + + /** Models the & operator: two strings give a string, anything else integers. */ + public function bitwiseAnd(self $right, PhpVersion $phpVersion): Type + { + return $this->calculateBitwise('&', $right, $phpVersion); + } + + /** Models the | operator: two strings give a string, anything else integers. */ + public function bitwiseOr(self $right, PhpVersion $phpVersion): Type + { + return $this->calculateBitwise('|', $right, $phpVersion); + } + + /** Models the ^ operator: two strings give a string, anything else integers. */ + public function bitwiseXor(self $right, PhpVersion $phpVersion): Type + { + return $this->calculateBitwise('^', $right, $phpVersion); + } + + /** Models the << operator; a negative shift is an error. */ + public function bitwiseShiftLeft(self $right, PhpVersion $phpVersion): Type + { + return $this->calculateShift('<<', $right, $phpVersion); + } + + /** Models the >> operator; a negative shift is an error. */ + public function bitwiseShiftRight(self $right, PhpVersion $phpVersion): Type + { + return $this->calculateShift('>>', $right, $phpVersion); + } + + /** + * @param '<<'|'>>' $operator + */ + private function calculateShift(string $operator, self $right, PhpVersion $phpVersion): Type + { + $result = $this->getResultOfOperands($right) ?? $this->fold($operator, $right); + if ($result !== null) { + return $result; + } + + [$left, $right] = $this->generalizeTooManyConstants($right); + if ($left->toNumberType() === null || $right->toNumberType() === null) { + return new ErrorType(); + } + + return $left->calculateCommonMath($operator, $right, $phpVersion); + } + + /** + * Operands with more pairs of constants than are computed one by one become the + * type all their constants are: int, float, string or null; anything else can + * only be an error. + * + * @return array{self, self} + */ + private function generalizeTooManyConstants(self $right): array + { + $leftTypes = $this->getFiniteScalarTypes(false); + $rightTypes = $right->getFiniteScalarTypes(false); + if ( + $leftTypes === null + || $rightTypes === null + || $leftTypes === [] + || $rightTypes === [] + || count($leftTypes) * count($rightTypes) <= InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT + ) { + return [$this, $right]; + } + + return [$this->generalize(), $right->generalize()]; + } + + private function generalize(): self + { + $hasBooleans = $this->false || $this->true; + $hasStrings = $this->constantStrings !== []; + $hasNumbers = $this->numbers !== null && !$this->numbers->isEmpty(); + if (!$hasBooleans && !$hasStrings && !$this->null && $hasNumbers) { + if ($this->numbers->getFloatIntervals() === [] && !$this->numbers->hasNan()) { + return self::createNumbers(new NumberSet([[null, null]], [], false)); + } + if ($this->numbers->getIntegerIntervals() === []) { + return self::createNumbers(new NumberSet([], [[-INF, INF]], true)); + } + } + if (!$hasBooleans && !$hasNumbers && !$this->null && $hasStrings) { + return self::createStrings(new StringType()); + } + if (!$hasBooleans && !$hasNumbers && !$hasStrings && $this->null) { + return self::createNull(); + } + + return self::createInvalid(); + } + + private function invalidOrFold(string $operator, self $right): ?Type + { + if ($this->invalid || $right->invalid) { + return new ErrorType(); + } + + return $this->fold($operator, $right); + } + + /** + * The arithmetic of + - * / << >> once the constants are folded: numbers are computed on + * their NumberSet, an operand that can be anything gives what its number can be. + * + * @param '+'|'-'|'*'|'/'|'<<'|'>>' $operator + */ + private function calculateCommonMath(string $operator, self $right, PhpVersion $phpVersion): Type + { + $leftNumberType = $this->toNumberType(); + $rightNumberType = $right->toNumberType(); + $isBenevolent = $this->benevolent + || $right->benevolent + || $leftNumberType instanceof BenevolentUnionType + || $rightNumberType instanceof BenevolentUnionType; + + $leftNumbers = $this->unknown || $right->unknown ? null : $leftNumberType?->toNumberSet(); + $rightNumbers = $this->unknown || $right->unknown ? null : $rightNumberType?->toNumberSet(); + if ($leftNumbers !== null && $rightNumbers !== null) { + if ($operator === '+') { + $result = $leftNumbers->add($rightNumbers, $phpVersion); + } elseif ($operator === '-') { + $result = $leftNumbers->subtract($rightNumbers, $phpVersion); + } elseif ($operator === '*') { + $result = $leftNumbers->multiply($rightNumbers, $phpVersion); + } elseif ($operator === '/') { + $result = $leftNumbers->divide($rightNumbers, $phpVersion); + } elseif ($operator === '<<') { + $result = $leftNumbers->shiftLeft($rightNumbers, $phpVersion); + } else { + $result = $leftNumbers->shiftRight($rightNumbers, $phpVersion); + } + + return $isBenevolent ? TypeUtils::toBenevolentUnion($result) : $result; + } + + if ($this->isArray()->yes() || $right->isArray()->yes()) { + return new ErrorType(); + } + if ($leftNumberType === null || $rightNumberType === null) { + return new ErrorType(); + } + if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) { + // keep an explicit never, it ends the execution + foreach ([$leftNumberType, $rightNumberType] as $numberType) { + if ($numberType instanceof NeverType && $numberType->isExplicit()) { + return $numberType; + } + } + + return new NeverType(); + } + + if ($leftNumberType->isFloat()->yes() || $rightNumberType->isFloat()->yes()) { + if (in_array($operator, ['<<', '>>'], true)) { + return new IntegerType(); + } + + return new FloatType(); + } + + $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); + $isUnknown = $this->unknown || $right->unknown; + if ($operator === '/') { + if ($isUnknown || $resultType->isInteger()->yes()) { + return new BenevolentUnionType([new IntegerType(), new FloatType()]); + } + + return new UnionType([new IntegerType(), new FloatType()]); + } + + if ($isUnknown || $this->benevolent || $right->benevolent) { + return TypeUtils::toBenevolentUnion($resultType); + } + + return $resultType; + } + + /** + * The number Type::toNumber() gives for the values, null when that is an error. A + * benevolent set leaves out the values that are not numbers, as long as any are left. + */ + private function toNumberType(): ?Type + { + if ($this->unknown) { + return TypeCombinator::union(new IntegerType(), new FloatType()); + } + if ($this->isEmpty()) { + return $this->never ?? new NeverType(); + } + + $types = []; + if ($this->null || $this->false) { + $types[] = new ConstantIntegerType(0); + } + if ($this->true) { + $types[] = new ConstantIntegerType(1); + } + if ($this->numbers !== null && !$this->numbers->isEmpty()) { + $types[] = self::createTypeOfNumbers($this->numbers); + } + + $otherTypes = []; + foreach ($this->constantStrings as $constantString) { + $otherTypes[] = new ConstantStringType($constantString); + } + foreach ([$this->strings, $this->arrays, $this->objects] as $type) { + if ($type === null) { + continue; + } + + $otherTypes[] = $type; + } + if ($this->resources) { + $otherTypes[] = new ResourceType(); + } + foreach ($otherTypes as $type) { + $numberType = $type->toNumber(); + if ($numberType instanceof ErrorType) { + if ($this->benevolent) { + continue; + } + + return null; + } + + $types[] = $numberType; + } + + if ($types === []) { + return null; + } + + return TypeCombinator::union(...$types); + } + + private function isArray(): TrinaryLogic + { + if ($this->unknown) { + return $this->unknownType?->isArray() ?? TrinaryLogic::createMaybe(); + } + if ($this->mayBeArray() && $this->mayBeNonArray()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createFromBoolean($this->mayBeArray()); + } + + private function isOnlyNonEmptyArrays(): bool + { + return $this->nonEmptyArray && !$this->emptyArray && !$this->mayBeNonArray(); + } + + /** + * Strings are combined byte by byte, so two strings give a string. A value that can be + * a string or something else, on both sides, can be either and is an error. + * + * @param '&'|'|'|'^' $operator + */ + private function calculateBitwise(string $operator, self $right, PhpVersion $phpVersion): Type + { + $result = $this->getResultOfOperands($right) ?? $this->fold($operator, $right); + if ($result !== null) { + return $result; + } + + $leftOnlyStrings = $this->isOnlyStrings(); + $rightOnlyStrings = $right->isOnlyStrings(); + if (($leftOnlyStrings || $this->unknown) && ($rightOnlyStrings || $right->unknown)) { + if ($leftOnlyStrings && $rightOnlyStrings) { + return new StringType(); + } + + // with an unknown operand that is not a string, the string is a number + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + if ($this->mayBeString() && !$leftOnlyStrings && $right->mayBeString() && !$rightOnlyStrings) { + return new ErrorType(); + } + + $leftNumbers = $this->toOperandNumbers(false, false); + $rightNumbers = $right->toOperandNumbers(false, false); + if ($leftNumbers === null || $rightNumbers === null) { + return new ErrorType(); + } + + if ($operator === '&') { + $result = $leftNumbers->bitwiseAnd($rightNumbers, $phpVersion); + } elseif ($operator === '|') { + $result = $leftNumbers->bitwiseOr($rightNumbers, $phpVersion); + } else { + $result = $leftNumbers->bitwiseXor($rightNumbers, $phpVersion); + } + + return $this->toResult($right, $result); + } + + /** The result decided by the operands alone: never for no values, an error for invalid ones. */ + private function getResultOfOperands(self $right): ?Type + { + if ($this->isEmpty() || $right->isEmpty()) { + // keep an explicit never, it ends the execution + foreach ([$this->never, $right->never] as $never) { + if ($never !== null && $never->isExplicit()) { + return $never; + } + } + + return new NeverType(); + } + + if ($this->invalid || $right->invalid) { + return new ErrorType(); + } + + return null; + } + + /** An error when the divisor is zero as a constant number, such as 0, '0', false or null. */ + private function getZeroDivisorError(): ?Type + { + foreach ($this->getFiniteScalarTypes(false) ?? [] as $type) { + if (in_array(self::getConstantNumber($type->toNumber()), [0, 0.0], true)) { + return new ErrorType(); + } + } + + return null; + } + + private function toResult(self $right, Type $result): Type + { + if ($this->benevolent || $this->unknown || $right->benevolent || $right->unknown) { + return TypeUtils::toBenevolentUnion($result); + } + + return $result; + } + + /** + * Computes the operator for each pair of values when both operands hold few enough + * constants. Null when they do not; an error when a pair is one. + */ + private function fold(string $operator, self $right): ?Type + { + // the bitwise operators list small integer ranges too + $expandIntegerIntervals = in_array($operator, ['&', '|', '^'], true); + $leftTypes = $this->getFiniteScalarTypes($expandIntegerIntervals); + $rightTypes = $right->getFiniteScalarTypes($expandIntegerIntervals); + if ( + $leftTypes === null + || $rightTypes === null + || $leftTypes === [] + || $rightTypes === [] + || count($leftTypes) * count($rightTypes) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT + ) { + return null; + } + + $resultTypes = []; + foreach ($leftTypes as $leftType) { + foreach ($rightTypes as $rightType) { + $resultType = self::foldValues($operator, $leftType, $rightType); + if ($resultType === null) { + return new ErrorType(); + } + + $resultTypes[] = $resultType; + } + } + + return TypeCombinator::union(...$resultTypes); + } + + /** Null for an error. */ + private static function foldValues(string $operator, ConstantScalarType $left, ConstantScalarType $right): ?Type + { + if ($operator === '**') { + return self::foldPower($left->getValue(), $right->getValue()); + } + + if (in_array($operator, ['&', '|', '^'], true) && $left->isString()->yes() && $right->isString()->yes()) { + $leftString = (string) $left->getValue(); + $rightString = (string) $right->getValue(); + if ($operator === '&') { + return new ConstantStringType($leftString & $rightString); + } + if ($operator === '|') { + return new ConstantStringType($leftString | $rightString); + } + + return new ConstantStringType($leftString ^ $rightString); + } + + $leftNumber = self::getConstantNumber($left->toNumber()); + $rightNumber = self::getConstantNumber($right->toNumber()); + if ($leftNumber === null || $rightNumber === null) { + return null; + } + + if ($operator === '+') { + return ConstantTypeHelper::getTypeFromValue($leftNumber + $rightNumber); + } + if ($operator === '-') { + return ConstantTypeHelper::getTypeFromValue($leftNumber - $rightNumber); + } + if ($operator === '*') { + return ConstantTypeHelper::getTypeFromValue($leftNumber * $rightNumber); + } + if ($operator === '/') { + if (in_array($rightNumber, [0, 0.0], true)) { + return null; + } + + return ConstantTypeHelper::getTypeFromValue($leftNumber / $rightNumber); + } + + // the integer operators read numeric strings out of the int range as the nearest + // integer, like an (int) cast, unlike the float toNumber() gives + $leftInteger = self::getConstantInteger($left->toInteger()); + $rightInteger = self::getConstantInteger($right->toInteger()); + if ($leftInteger === null || $rightInteger === null) { + return null; + } + + if ($operator === '%') { + return $rightInteger === 0 ? null : new ConstantIntegerType($leftInteger % $rightInteger); + } + if ($operator === '<<') { + return $rightInteger < 0 ? null : new ConstantIntegerType($leftInteger << $rightInteger); + } + if ($operator === '>>') { + return $rightInteger < 0 ? null : new ConstantIntegerType($leftInteger >> $rightInteger); + } + if ($operator === '&') { + return new ConstantIntegerType($leftInteger & $rightInteger); + } + if ($operator === '|') { + return new ConstantIntegerType($leftInteger | $rightInteger); + } + + return new ConstantIntegerType($leftInteger ^ $rightInteger); + } + + /** Null for an error. */ + private static function foldPower(bool|float|int|string|null $base, bool|float|int|string|null $exponent): ?Type + { + $result = self::pow($base, $exponent); + if ($result === null) { + return null; + } + + $resultType = ConstantTypeHelper::getTypeFromValue($result); + + // a zero float stands for both zeros (0.0 === -0.0), and a negative odd exponent tells them apart + if (is_float($base) && $base === 0.0) { + $otherZeroResult = self::pow(-$base, $exponent); + if (is_float($otherZeroResult) && $otherZeroResult !== $result) { + return TypeCombinator::union($resultType, new ConstantFloatType($otherZeroResult)); + } + } + + return $resultType; + } + + private static function pow(mixed $base, mixed $exponent): float|int|null + { + if ((is_string($base) && !is_numeric($base)) || (is_string($exponent) && !is_numeric($exponent))) { + return null; + } + + // PHP 8.4 deprecates 0 ** -1, which still gives INF + return @pow($base, $exponent); + } + + private static function getConstantNumber(Type $numberType): int|float|null + { + $values = $numberType->getConstantScalarValues(); + if (count($values) !== 1 || (!is_int($values[0]) && !is_float($values[0]))) { + return null; + } + + return $values[0]; + } + + private static function getConstantInteger(Type $integerType): ?int + { + $values = $integerType->getConstantScalarValues(); + if (count($values) !== 1 || !is_int($values[0])) { + return null; + } + + return $values[0]; + } + + /** + * The values of a set of constants, null for any other set. Bounded integer ranges + * are listed too when asked for, up to the limit of the constants computed. + * + * @return list|null + */ + private function getFiniteScalarTypes(bool $expandIntegerIntervals): ?array + { + if ( + $this->unknown + || $this->strings !== null + || $this->emptyArray + || $this->nonEmptyArray + || $this->objects !== null + || $this->resources + ) { + return null; + } + + $types = []; + if ($this->null) { + $types[] = new NullType(); + } + if ($this->false) { + $types[] = new ConstantBooleanType(false); + } + if ($this->true) { + $types[] = new ConstantBooleanType(true); + } + if ($this->numbers !== null) { + foreach ($this->numbers->getIntegerIntervals() as [$min, $max]) { + if ($min === null || $max === null) { + return null; + } + // only a range on its own is listed, as IntegerRangeType::getFiniteTypes() lists it + if ( + $min !== $max + && ( + !$expandIntegerIntervals + || $max - $min > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT + || !$this->isOnlyOneIntegerInterval() + ) + ) { + return null; + } + + // $min + $i stays within $max, unlike $i++ past PHP_INT_MAX, which would turn it into a float + for ($i = 0; $i <= $max - $min; $i++) { + $types[] = new ConstantIntegerType($min + $i); + } + } + foreach ($this->numbers->getFloatIntervals() as [$min, $max]) { + if ($min !== $max) { + return null; + } + + $types[] = new ConstantFloatType($min); + } + if ($this->numbers->hasNan()) { + $types[] = new ConstantFloatType(NAN); + } + } + foreach ($this->constantStrings as $constantString) { + $types[] = new ConstantStringType($constantString); + } + + return $types; + } + + /** + * The numbers an arithmetic operator reads from the values, null when one of them is + * not a number operand; a benevolent set leaves those out as long as any are left. + * Arrays are left out when asked for, the + operator adds them. The integer operators + * read their operands as integers, numeric strings out of the int range as the nearest + * one, like an (int) cast. + */ + private function toOperandNumbers(bool $withoutArrays, bool $integers): ?NumberSet + { + if ($this->unknown) { + return $integers ? new NumberSet([[null, null]], [], false) : new NumberSet([[null, null]], [[-INF, INF]], true); + } + if (((!$withoutArrays && ($this->emptyArray || $this->nonEmptyArray)) || $this->resources) && !$this->benevolent) { + return null; + } + + $sets = []; + if ($this->null || $this->false) { + $sets[] = new NumberSet([[0, 0]], [], false); + } + if ($this->true) { + $sets[] = new NumberSet([[1, 1]], [], false); + } + if ($this->numbers !== null) { + $sets[] = $integers ? self::toIntegerNumbers($this->numbers) : $this->numbers; + } + + $types = []; + foreach ($this->constantStrings as $constantString) { + $types[] = new ConstantStringType($constantString); + } + if ($this->strings !== null) { + $types[] = $this->strings; + } + if ($this->objects !== null) { + $types[] = $this->objects; + } + foreach ($types as $type) { + $numbers = $type->toNumber()->toNumberSet(); + if ($numbers === null) { + if ($this->benevolent) { + continue; + } + + return null; + } + + $sets[] = $integers ? $type->toInteger()->toNumberSet() ?? new NumberSet([[null, null]], [], false) : $numbers; + } + + if ($sets === []) { + return null; + } + + // a value of two kinds that read as the same number is one member + $integerIntervals = []; + $floatIntervals = []; + $nan = false; + foreach ($sets as $set) { + foreach ($set->getIntegerIntervals() as $interval) { + if (in_array($interval, $integerIntervals, true)) { + continue; + } + + $integerIntervals[] = $interval; + } + foreach ($set->getFloatIntervals() as $interval) { + if (in_array($interval, $floatIntervals, true)) { + continue; + } + + $floatIntervals[] = $interval; + } + $nan = $nan || $set->hasNan(); + } + + return new NumberSet($integerIntervals, $floatIntervals, $nan); + } + + /** The integers the numbers become, as an (int) cast converts them. */ + private static function toIntegerNumbers(NumberSet $numbers): NumberSet + { + $integers = new NumberSet($numbers->getIntegerIntervals(), [], false); + $floatTypes = []; + foreach ($numbers->getFloatIntervals() as [$min, $max]) { + $floatTypes[] = $min === $max ? new ConstantFloatType($min) : FloatRangeType::fromInterval($min, $max); + } + if ($numbers->hasNan()) { + $floatTypes[] = new ConstantFloatType(NAN); + } + foreach ($floatTypes as $floatType) { + $integers = $integers->union($floatType->toInteger()->toNumberSet() ?? new NumberSet([[null, null]], [], false)); + } + + return $integers; + } + + private function isOnlyOneIntegerInterval(): bool + { + return $this->numbers !== null + && count($this->numbers->getIntegerIntervals()) === 1 + && $this->numbers->getFloatIntervals() === [] + && !$this->numbers->hasNan() + && !$this->mayBeNonNumber(); + } + + private function isOnlyIntegers(): bool + { + return $this->numbers !== null + && $this->numbers->getFloatIntervals() === [] + && !$this->numbers->hasNan() + && !$this->mayBeNonNumber(); + } + + private function isOnlyStrings(): bool + { + return $this->mayBeString() + && !$this->unknown + && !$this->null + && !$this->false + && !$this->true + && $this->numbers === null + && !$this->mayBeArray() + && $this->objects === null + && !$this->resources; + } + + private function mayBeString(): bool + { + return $this->unknown || $this->constantStrings !== [] || $this->strings !== null; + } + + private function mayBeArray(): bool + { + return $this->unknown || $this->emptyArray || $this->nonEmptyArray; + } + + private function mayBeNonArray(): bool + { + return $this->unknown + || $this->null + || $this->false + || $this->true + || $this->numbers !== null + || $this->constantStrings !== [] + || $this->strings !== null + || $this->objects !== null + || $this->resources; + } + + private function mayBeNonNumber(): bool + { + return $this->unknown + || $this->null + || $this->false + || $this->true + || $this->constantStrings !== [] + || $this->strings !== null + || $this->mayBeArray() + || $this->objects !== null + || $this->resources; + } + private static function unionTypes(?Type $left, ?Type $right): ?Type { if ($left === null || $right === null) { diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 7f9386e6ce7..ac31159c7e2 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -295,4 +295,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createInvalid(); + } + } diff --git a/turbo-ext/src/AccessoryArrayListType.cpp b/turbo-ext/src/AccessoryArrayListType.cpp index da5c5c0eb0d..16d3f2aa661 100644 --- a/turbo-ext/src/AccessoryArrayListType.cpp +++ b/turbo-ext/src/AccessoryArrayListType.cpp @@ -360,7 +360,7 @@ PT_MINIT_REGISTRATION(pt_register_accessory_array_list_type) ptdecl::AccessoryArrayListType::declareClass(cls); ptdecl::AccessoryArrayListType::declareProperties(cls); /* mirrors toValueSet() */ - cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS(true, true)); + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/BenevolentUnionType.cpp b/turbo-ext/src/BenevolentUnionType.cpp index 3163402d720..e93e1291ee5 100644 --- a/turbo-ext/src/BenevolentUnionType.cpp +++ b/turbo-ext/src/BenevolentUnionType.cpp @@ -552,6 +552,13 @@ PT_MINIT_REGISTRATION(pt_register_benevolent_union_type) PT_RETURN_VAL(PT_THIS.traverseSimultaneously(right, &fci, &fcc)); }); + /* parent::toValueSet()->toBenevolent() */ + cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + zv::Val valueSet = pt_type_call_parent(pt_ce_benevolent_union_type, Z_OBJ_P(ZEND_THIS), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(valueSet.isUndef())) RETURN_THROWS(); + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(valueSet.raw()), PT_LC("tobenevolent"), 0, NULL)); + }); cls.shadow(&pt_ce_benevolent_union_type); } diff --git a/turbo-ext/src/BooleanType.cpp b/turbo-ext/src/BooleanType.cpp index fa5b18bb3bd..58cfad812cc 100644 --- a/turbo-ext/src/BooleanType.cpp +++ b/turbo-ext/src/BooleanType.cpp @@ -142,12 +142,10 @@ class BooleanType /* [new ConstantBooleanType(true), new ConstantBooleanType(false)] */ static zv::Val getFiniteTypes() { return bothConstants(); } - /* ExponentiateHelper::exponentiate($this, $exponent) */ + /* $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion) */ 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); + return pt_value_set_power(self, exponent, phpVersion); } /* new IdentifierTypeNode('bool') */ diff --git a/turbo-ext/src/CallableType.cpp b/turbo-ext/src/CallableType.cpp index ad6439a153a..49d7606a279 100644 --- a/turbo-ext/src/CallableType.cpp +++ b/turbo-ext/src/CallableType.cpp @@ -1221,6 +1221,8 @@ PT_MINIT_REGISTRATION(pt_register_callable_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("iscallableallowed"), PT_CLASS_IDENTIFIER, PT_LC("callable"))); }); + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createinvalid")); + /* the traits, in the twin's `use` order; the class body above wins over * every name it declares */ ptdecl::CallableType::registerTraits(cls); diff --git a/turbo-ext/src/FloatType.cpp b/turbo-ext/src/FloatType.cpp index 244716075bd..c3d7c38bcd0 100644 --- a/turbo-ext/src/FloatType.cpp +++ b/turbo-ext/src/FloatType.cpp @@ -218,12 +218,10 @@ class FloatType /* $this */ zv::Val traverseSimultaneously() const { return thisValue(); } - /* ExponentiateHelper::exponentiate($this, $exponent) */ + /* $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion) */ 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); + return pt_value_set_power(self, exponent, phpVersion); } /* new IdentifierTypeNode('float') */ diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h index a15ddd35101..9fa9004a2a3 100644 --- a/turbo-ext/src/IetrSupport.h +++ b/turbo-ext/src/IetrSupport.h @@ -18,11 +18,8 @@ namespace phpstanturbo::ietr { -/* InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT and - * OperatorTypeHelper's private IS_* codes */ +/* InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT */ 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); \ diff --git a/turbo-ext/src/IntegerType.cpp b/turbo-ext/src/IntegerType.cpp index 1c6fbea304f..d239aae2c99 100644 --- a/turbo-ext/src/IntegerType.cpp +++ b/turbo-ext/src/IntegerType.cpp @@ -192,12 +192,10 @@ class IntegerType static zv::Val getFiniteTypes() { return zv::Val(zv::Arr::empty()); } - /* ExponentiateHelper::exponentiate($this, $exponent) */ + /* $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion) */ 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); + return pt_value_set_power(self, exponent, phpVersion); } /* new IdentifierTypeNode('int') */ diff --git a/turbo-ext/src/IterableType.cpp b/turbo-ext/src/IterableType.cpp index 1037b265bb5..75d4b892878 100644 --- a/turbo-ext/src/IterableType.cpp +++ b/turbo-ext/src/IterableType.cpp @@ -1031,6 +1031,8 @@ PT_MINIT_REGISTRATION(pt_register_iterable_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("isiterableallowed"), PT_CLASS_IDENTIFIER, PT_LC("iterable"))); }); + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createinvalid")); + /* the traits, in the twin's `use` order; the class body above wins over * every name it declares */ ptdecl::IterableType::registerTraits(cls); diff --git a/turbo-ext/src/MixedType.cpp b/turbo-ext/src/MixedType.cpp index 58c2816dc1c..3776d1cf7ac 100644 --- a/turbo-ext/src/MixedType.cpp +++ b/turbo-ext/src/MixedType.cpp @@ -1907,6 +1907,8 @@ PT_MINIT_REGISTRATION(pt_register_mixed_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("ismixedallowed"), PT_CLASS_IDENTIFIER, PT_LC("mixed"))); }); + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createunknown")); + /* the traits, in the twin's `use` order (UndecidedComparisonCompoundTypeTrait * brings UndecidedComparisonTypeTrait with it); the class body above wins * over every name it declares (isCallable, getCallableParametersAcceptors, diff --git a/turbo-ext/src/NeverType.cpp b/turbo-ext/src/NeverType.cpp index 16917c07da2..8e2674a75e4 100644 --- a/turbo-ext/src/NeverType.cpp +++ b/turbo-ext/src/NeverType.cpp @@ -284,7 +284,7 @@ PT_MINIT_REGISTRATION(pt_register_never_type) * (slots::isExplicit, slots::reason) */ ptdecl::NeverType::declareProperties(cls); /* mirrors toValueSet() */ - cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createempty")); + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createnever")); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { bool isExplicit = false; diff --git a/turbo-ext/src/NonEmptyArrayType.cpp b/turbo-ext/src/NonEmptyArrayType.cpp index 41f52528a58..85eb16a6daf 100644 --- a/turbo-ext/src/NonEmptyArrayType.cpp +++ b/turbo-ext/src/NonEmptyArrayType.cpp @@ -317,7 +317,7 @@ PT_MINIT_REGISTRATION(pt_register_non_empty_array_type) ptdecl::NonEmptyArrayType::declareClass(cls); ptdecl::NonEmptyArrayType::declareProperties(cls); /* mirrors toValueSet() */ - cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS(false, true)); + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/NonexistentParentClassType.cpp b/turbo-ext/src/NonexistentParentClassType.cpp index 840ecf9c399..1c0872d5c6a 100644 --- a/turbo-ext/src/NonexistentParentClassType.cpp +++ b/turbo-ext/src/NonexistentParentClassType.cpp @@ -217,6 +217,8 @@ PT_MINIT_REGISTRATION(pt_register_nonexistent_parent_class_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("isclassnameallowed"), PT_CLASS_NAME, PT_LC("parent"))); }); + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createinvalid")); + /* the traits, in the twin's `use` order; the class body above wins over * every name it declares */ ptdecl::NonexistentParentClassType::registerTraits(cls); diff --git a/turbo-ext/src/NullType.cpp b/turbo-ext/src/NullType.cpp index 62aed52a7dd..52ff69cd9dc 100644 --- a/turbo-ext/src/NullType.cpp +++ b/turbo-ext/src/NullType.cpp @@ -692,14 +692,11 @@ PT_MINIT_REGISTRATION(pt_register_null_type) cls.method<&NullType::getFiniteTypes>(sigs::getFiniteTypes); - /* ExponentiateHelper::exponentiate($this, $exponent) */ + /* $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion) */ cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { zval *exponent, *phpVersion; if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); - 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)); + PT_RETURN_VAL(pt_value_set_power(Z_OBJ_P(ZEND_THIS), exponent, phpVersion)); }); cls.method<&NullType::toPhpDocNode>(sigs::toPhpDocNode); diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp index 6aabcbcd05d..0ee41f98a64 100644 --- a/turbo-ext/src/NumberSet.cpp +++ b/turbo-ext/src/NumberSet.cpp @@ -730,6 +730,140 @@ class NumberSetMath } public: + /* Mirrors power(): PHP null for its null */ + static zv::Val power(const NumberSetData &base, const NumberSetData &exponent) + { + const NumberSetData::IntegerInterval *baseBounds = getBoundedIntegerInterval(base); + const NumberSetData::IntegerInterval *exponentBounds = getBoundedIntegerInterval(exponent); + if (baseBounds == nullptr || exponentBounds == nullptr) { + return zv::Val::null(); + } + + return powerIntegerIntervals(baseBounds->min, baseBounds->max, exponentBounds->min, exponentBounds->max); + } + + /* Mirrors getBoundedIntegerInterval(): nullptr for its null */ + static const NumberSetData::IntegerInterval *getBoundedIntegerInterval(const NumberSetData &set) + { + if (!set.floatIntervals.empty() || set.nan || set.integerIntervals.size() != 1) return nullptr; + const NumberSetData::IntegerInterval &interval = set.integerIntervals[0]; + if (interval.min.isNull && interval.max.isNull) return nullptr; + return &interval; + } + + /* Mirrors powerIntegerIntervals(): PHP null for its null */ + static zv::Val powerIntegerIntervals(NullableLong baseMin, NullableLong baseMax, NullableLong exponentMin, NullableLong exponentMax) + { + if (exponentMin.isNull || exponentMin.value < 0) { + return zv::Val::null(); + } + + if (!exponentMax.isNull && exponentMax.value == 0) { + return newConstantIntegerType(1); + } + + if (!baseMin.isNull && baseMin.value >= 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 + NullableLong min = baseMin.value == 0 ? NullableLong::of(0) : integerPower(baseMin.value, exponentMin.value); + NullableLong max; + if (baseMax.isNull || (baseMax.value > 1 && exponentMax.isNull)) { + max = NullableLong::null(); + } else if (baseMax.value == 0) { + max = NullableLong::of(exponentMin.value == 0 ? 1 : 0); + } else if (exponentMax.isNull) { + max = NullableLong::of(1); + } else { + max = integerPower(baseMax.value, exponentMax.value); + if (max.isNull) { + return zv::Val::null(); + } + } + + if (min.isNull) { + return zv::Val::null(); + } + + return fromInterval(min, max); + } + + // a negative base alternates the sign with the parity of the exponent + if (exponentMax.isNull || exponentMax.value - exponentMin.value >= PT_IETR_CALCULATE_SCALARS_LIMIT) { + return zv::Val::null(); + } + + zv::Arr results = zv::Arr::create((uint32_t) (exponentMax.value - exponentMin.value + 1)); + for (zend_long i = 0; i <= exponentMax.value - exponentMin.value; i++) { + bool overflow = false; + IETR_VAL(result, powerIntegerInterval(baseMin, baseMax, exponentMin.value + i, overflow)); + if (overflow) { + return zv::Val::null(); + } + results.push(std::move(result)); + } + + return unionOf(results); + } + + /* Mirrors powerIntegerInterval(): `overflow` for its null */ + static zv::Val powerIntegerInterval(NullableLong baseMin, NullableLong baseMax, zend_long exponent, bool &overflow) + { + if (exponent == 0) { + return newConstantIntegerType(1); + } + + NullableLong minBase, maxBase; + if (exponent % 2 == 1) { + // an odd power keeps the order of the bases + minBase = baseMin; + maxBase = baseMax; + } else if (!baseMax.isNull && baseMax.value <= 0) { + // an even power of non-positive bases reverses their order + minBase = baseMax; + maxBase = baseMin; + } else if (!baseMin.isNull && baseMin.value >= 0) { + minBase = baseMin; + maxBase = baseMax; + } else { + // an even power of bases around 0 is the largest at the end further from 0 + if (!baseMin.isNull && baseMin.value == ZEND_LONG_MIN) { + // -PHP_INT_MIN overflows into a float + overflow = true; + return zv::Val::null(); + } + minBase = NullableLong::of(0); + maxBase = baseMin.isNull || baseMax.isNull ? NullableLong::null() : NullableLong::of(std::max(-baseMin.value, baseMax.value)); + } + + NullableLong min = minBase.isNull ? NullableLong::null() : integerPower(minBase.value, exponent); + NullableLong max = maxBase.isNull ? NullableLong::null() : integerPower(maxBase.value, exponent); + if ((!minBase.isNull && min.isNull) || (!maxBase.isNull && max.isNull)) { + overflow = true; + return zv::Val::null(); + } + + return fromInterval(min, max); + } + + /* Mirrors integerPower(): $base ** $exponent of a non-negative exponent, null when + * it overflows into a float; PHP gives an integer exactly when the power fits */ + static NullableLong integerPower(zend_long base, zend_long exponent) + { + zend_long result = 1; + zend_long factor = base; + while (exponent > 0) { + if (exponent % 2 == 1) { + if (__builtin_mul_overflow(result, factor, &result)) return NullableLong::null(); + exponent--; + continue; + } + exponent /= 2; + if (__builtin_mul_overflow(factor, factor, &factor)) return NullableLong::null(); + } + + return NullableLong::of(result); + } + /* Mirrors intDivide(). */ static zv::Val intDivide(const NumberSetData &left, const NumberSetData &right) { @@ -1154,6 +1288,23 @@ PT_MINIT_REGISTRATION(pt_register_number_set) if (UNEXPECTED(!PT_THIS.read(set))) RETURN_THROWS(); PT_RETURN_VAL(phpstanturbo::NumberSetMath::negate(set, phpVersion)); }); + cls.method(sigs::isEmpty, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + NumberSetData set; + if (UNEXPECTED(!PT_THIS.read(set))) RETURN_THROWS(); + RETURN_BOOL(set.integerIntervals.empty() && set.floatIntervals.empty() && !set.nan); + }); + cls.method(sigs::power, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *exponent, *phpVersion; + if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(exponent), pt_ce_number_set))) { + zend_type_error("%s::power(): Argument #1 ($exponent) must be of type %s, %s given", ZSTR_VAL(pt_ce_number_set->name), ZSTR_VAL(pt_ce_number_set->name), zend_zval_value_name(exponent)); + RETURN_THROWS(); + } + NumberSetData base, exponentData; + if (UNEXPECTED(!PT_THIS.read(base) || !NumberSet(Z_OBJ_P(exponent)).read(exponentData))) RETURN_THROWS(); + PT_RETURN_VAL(phpstanturbo::NumberSetMath::power(base, exponentData)); + }); cls.method(sigs::shiftLeft, pt_ns_operator); cls.method(sigs::shiftRight, pt_ns_operator); diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 503fb340c4d..34cb376af8a 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -1,20 +1,14 @@ /* * 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). + * PHPStan\Type\OperatorTypeHelper: the . operator (Type::stringConcatenate()) + * and the array + operator (Type::arrayUnion()) OperatorTypeTrait delegates + * to (pt_type_trait_operator() in TypeTraits.cpp calls + * pt_operator_type_helper_binary() directly). The arithmetic and bitwise + * operators are computed on ValueSet and NumberSet. * * 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. Only concat() - * (trailing whitespace in numeric strings) and the float interval division - * (whether a zero divisor throws) read the PhpVersion; the other operators - * accept it and pass it along. + * private ones are registered too so the method sets stay identical. Only + * concat() reads the PhpVersion (trailing whitespace in numeric strings). */ #include "support.h" @@ -236,183 +230,6 @@ class OperatorTypeHelper 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) - ) { - /* with a mixed operand that is not a string, the string is a number */ - if (leftIsString == PT_TRI_YES && rightIsString == PT_TRI_YES) { - return newStringType(); - } - - 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); - } - 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(); - } - - NumberSetData leftNumbers, rightNumbers; - bool leftHas = false, rightHas = false; - if (UNEXPECTED(!pt_type_to_number_set(leftNumberType.raw(), leftNumbers, leftHas))) return zv::Val(); - if (UNEXPECTED(!pt_type_to_number_set(rightNumberType.raw(), rightNumbers, rightHas))) return zv::Val(); - if (!leftHas) leftNumbers = anyInteger(); - if (!rightHas) rightNumbers = anyInteger(); - - return pt_number_set_bitwise(kind, leftNumbers, rightNumbers); - } - - /* 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; - } - - 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()); - } - if (UNEXPECTED(resultType.isUndef())) return -1; - resultTypes.push(std::move(resultType)); - } - } - result = unionOf(resultTypes); - 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; - } - /* Mirrors canEndInWhitespace(): 1 / 0, -1 = pending exception */ static int canEndInWhitespace(zval *left, zval *leftStringType, zval *phpVersion) { @@ -451,407 +268,9 @@ class OperatorTypeHelper 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) - { - 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; - if (UNEXPECTED(!toIntegerOperand(rightEntry.value().raw(), rightInteger))) return -1; - if (Z_LVAL(rightInteger) == 0) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - zval leftInteger; - 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 rightInteger; - if (UNEXPECTED(!toIntegerOperand(rightEntry.value().raw(), rightInteger))) return -1; - if (Z_LVAL(rightInteger) < 0) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - 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; - } - 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, zval *phpVersion) - { - IETR_VAL(result, getDivTypeFromTypes(leftType, rightType, phpVersion)); - - 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, zval *phpVersion) - { - 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, phpVersion); - } - - /* 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(); - } - } - } - - NumberSetData leftNumbers, rightNumbers; - { - IETR_VAL(leftInteger, callOn(leftType, mToInteger)); - if (UNEXPECTED(!getIntegerNumberSetOrAny(leftInteger.raw(), leftNumbers))) return zv::Val(); - IETR_VAL(rightInteger, callOn(rightType, mToInteger)); - if (UNEXPECTED(!getIntegerNumberSetOrAny(rightInteger.raw(), rightNumbers))) return zv::Val(); - } - - return pt_number_set_modulo(leftNumbers, rightNumbers); - } - - /* Mirrors getIntegerIntervals(): the integer intervals of a type that - * holds integers and nothing else, has = false for its null; false = - * pending exception */ - [[nodiscard]] static bool getIntegerIntervals(zval *type, std::vector &intervals, bool &has) - { - NumberSetData numberSet; - if (UNEXPECTED(!pt_type_to_number_set(type, numberSet, has))) return false; - if (!has) return true; - - if (!numberSet.floatIntervals.empty() || numberSet.nan || numberSet.integerIntervals.empty()) { - has = false; - return true; - } - - intervals = std::move(numberSet.integerIntervals); - return true; - } - - /* getIntegerIntervals() as the private method returns it: a list of - * [min, max] pairs or null; UNDEF = pending exception */ - static zv::Val getIntegerIntervalsValue(zval *type) - { - std::vector intervals; - bool has = false; - if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return zv::Val(); - if (!has) return zv::Val::null(); - return integerIntervalsValue(intervals); - } - - static zv::Val integerIntervalsValue(const std::vector &intervals) - { - zv::Arr list = zv::Arr::create((uint32_t) intervals.size()); - for (const NumberSetData::IntegerInterval &interval : intervals) { - list.push(integerIntervalValue(interval)); - } - return zv::Val(std::move(list)); - } - - static zv::Val integerIntervalValue(const NumberSetData::IntegerInterval &interval) - { - zv::Arr pair = zv::Arr::create(2); - pair.push(interval.min.toVal()); - pair.push(interval.max.toVal()); - return zv::Val(std::move(pair)); - } - - /* Mirrors add(). */ - static zv::Val add(zval *leftType, zval *rightType, zval *phpVersion) - { - 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, phpVersion); - } + /* {{{ arrayUnion() */ /* Mirrors arrayUnion(). */ static zv::Val arrayUnion(zval *leftType, zval *rightType) @@ -1056,326 +475,6 @@ class OperatorTypeHelper return zv::Val::null(); } - /* Mirrors subtract() / multiply() (kind IETR_MINUS / IETR_MUL). */ - static zv::Val subtractOrMultiply(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) - { - 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) { - /* 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); - } - } - - return resolveCommonMath(kind, leftType, rightType, phpVersion); - } - - /* Mirrors shiftLeft() / shiftRight(). */ - static zv::Val shift(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) - { - if (isA(leftType, pt_ce_never_type) || isA(rightType, pt_ce_never_type)) { - return getNeverType(leftType, rightType); - } - - 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, phpVersion); - } - - /* 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); - } - - /* }}} */ - - /* {{{ resolveCommonMath() */ - - /* Mirrors resolveCommonMath() ($operator as `kind`). */ - static zv::Val resolveCommonMath(IetrBinaryKind kind, zval *leftType, zval *rightType, zval *phpVersion) - { - IETR_VAL(types, union2(leftType, rightType)); - IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); - IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - - bool typesMixed = isA(types.raw(), pt_ce_mixed_type); - if (!typesMixed) { - NumberSetData leftNumberSet, rightNumberSet; - bool leftHas = false, rightHas = false; - if (UNEXPECTED(!pt_type_to_number_set(leftNumberType.raw(), leftNumberSet, leftHas))) return zv::Val(); - if (UNEXPECTED(!pt_type_to_number_set(rightNumberType.raw(), rightNumberSet, rightHas))) return zv::Val(); - if (leftHas && rightHas) { - IETR_VAL(result, pt_number_set_calculate(kind, leftNumberSet, rightNumberSet, phpVersion)); - if ( - isA(leftNumberType.raw(), pt_ce_benevolent_union_type) - || isA(rightNumberType.raw(), pt_ce_benevolent_union_type) - || isA(leftType, pt_ce_benevolent_union_type) - || isA(rightType, pt_ce_benevolent_union_type) - ) { - return toBenevolentUnion(result.raw()); - } - - return result; - } - } - - { - 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; - } - - /* }}} */ - - /* {{{ negate() */ - - /* Mirrors negate(). */ - static zv::Val negate(zval *type, zval *phpVersion) - { - IETR_VAL(number, callOn(type, mToNumber)); - NumberSetData numberSet; - bool has = false; - if (UNEXPECTED(!pt_type_to_number_set(number.raw(), numberSet, has))) return zv::Val(); - if (!has) { - return number; - } - - IETR_VAL(negated, pt_number_set_negate(numberSet, phpVersion)); - if (isA(number.raw(), pt_ce_benevolent_union_type)) return toBenevolentUnion(negated.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); - } - - /* }}} */ - - /* {{{ intDivide() */ - - /* Mirrors intDivide(). */ - static zv::Val intDivide(zval *numeratorType, zval *divisorType) - { - NumberSetData numerator, divisor; - bool numeratorHas = false, divisorHas = false; - { - IETR_VAL(numeratorInteger, callOn(numeratorType, mToInteger)); - if (UNEXPECTED(!getIntegerNumberSet(numeratorInteger.raw(), numerator, numeratorHas))) return zv::Val(); - IETR_VAL(divisorInteger, callOn(divisorType, mToInteger)); - if (UNEXPECTED(!getIntegerNumberSet(divisorInteger.raw(), divisor, divisorHas))) return zv::Val(); - } - if (!numeratorHas || !divisorHas) { - return newErrorType(); - } - - return pt_number_set_int_divide(numerator, divisor); - } - - /* Mirrors getIntegerNumberSet(): `has` false for its null; false = - * pending exception */ - [[nodiscard]] static bool getIntegerNumberSet(zval *type, NumberSetData &out, bool &has) - { - std::vector intervals; - if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return false; - if (!has) { - zend_long isInteger = triOn(type, mIsInteger); - if (UNEXPECTED(isInteger < 0)) return false; - if (isInteger != PT_TRI_YES) return true; - out = anyInteger(); - has = true; - return true; - } - - out.integerIntervals = std::move(intervals); - return true; - } - - /* getIntegerNumberSet() or any integer: the `?? $anyInteger` of the twin */ - [[nodiscard]] static bool getIntegerNumberSetOrAny(zval *type, NumberSetData &out) - { - bool has = false; - if (UNEXPECTED(!getIntegerNumberSet(type, out, has))) return false; - if (!has) out = anyInteger(); - return true; - } - - /* getIntegerNumberSet() as the private method returns it: a NumberSet or - * null; UNDEF = pending exception */ - static zv::Val getIntegerNumberSetValue(zval *type) - { - NumberSetData set; - bool has = false; - if (UNEXPECTED(!getIntegerNumberSet(type, set, has))) return zv::Val(); - if (!has) return zv::Val::null(); - return pt_number_set_new(set); - } - - /* new NumberSet([[null, null]], [], false) */ - static NumberSetData anyInteger() - { - NumberSetData set; - set.integerIntervals.push_back({ NullableLong::null(), NullableLong::null() }); - return set; - } - /* }}} */ }; @@ -1389,28 +488,6 @@ using phpstanturbo::OperatorTypeHelper; zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion) { switch (op) { - case PT_OPERATOR_ADD: - return OperatorTypeHelper::add(left, right, phpVersion); - case PT_OPERATOR_SUBTRACT: - return OperatorTypeHelper::subtractOrMultiply(IETR_MINUS, left, right, phpVersion); - case PT_OPERATOR_MULTIPLY: - return OperatorTypeHelper::subtractOrMultiply(IETR_MUL, left, right, phpVersion); - case PT_OPERATOR_DIVIDE: - return OperatorTypeHelper::divide(left, right, phpVersion); - 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, phpVersion); - case PT_OPERATOR_SHIFT_RIGHT: - return OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, left, right, phpVersion); case PT_OPERATOR_CONCATENATE: return OperatorTypeHelper::concat(left, right, phpVersion); case PT_OPERATOR_ARRAY_UNION: @@ -1420,11 +497,6 @@ zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval return zv::Val(); } -zv::Val pt_operator_type_helper_negate(zval *type, zval *phpVersion) -{ - return OperatorTypeHelper::negate(type, phpVersion); -} - /* }}} */ /* {{{ engine ABI glue: parameter parsing + registration */ @@ -1453,30 +525,6 @@ bool otArgPhpVersion(zval *value, uint32_t arg) /* the operator string of resolveCommonMath(); 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) { \ @@ -1510,44 +558,11 @@ 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, 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))); - - 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, phpVersion))); - cls.method(sigs::getDivTypeFromTypes, OT_BINARY_GLUE(OperatorTypeHelper::getDivTypeFromTypes(leftType, rightType, phpVersion))); - cls.method(sigs::modulo, OT_BINARY_GLUE(OperatorTypeHelper::modulo(leftType, rightType))); - - cls.method(sigs::getIntegerNumberSet, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerNumberSetValue(type))); - cls.method(sigs::getIntegerIntervals, OT_TYPE_GLUE(OperatorTypeHelper::getIntegerIntervalsValue(type))); - cls.method(sigs::add, OT_BINARY_GLUE(OperatorTypeHelper::add(leftType, rightType, phpVersion))); cls.method(sigs::arrayUnion, OT_BINARY_GLUE(OperatorTypeHelper::arrayUnion(leftType, rightType))); cls.method(sigs::tryArrayUnion, OT_TYPES_GLUE(OperatorTypeHelper::tryArrayUnion(leftType, rightType))); - cls.method(sigs::subtract, OT_BINARY_GLUE(OperatorTypeHelper::subtractOrMultiply(IETR_MINUS, leftType, rightType, phpVersion))); - cls.method(sigs::multiply, OT_BINARY_GLUE(OperatorTypeHelper::subtractOrMultiply(IETR_MUL, leftType, rightType, phpVersion))); - cls.method(sigs::shiftLeft, OT_BINARY_GLUE(OperatorTypeHelper::shift(IETR_SHIFT_LEFT, leftType, rightType, phpVersion))); - cls.method(sigs::shiftRight, OT_BINARY_GLUE(OperatorTypeHelper::shift(IETR_SHIFT_RIGHT, leftType, rightType, phpVersion))); - cls.method(sigs::optimizeScalarType, OT_TYPE_GLUE(OperatorTypeHelper::optimizeScalarType(type))); cls.method(sigs::canEndInWhitespace, [](INTERNAL_FUNCTION_PARAMETERS) { zval *left, *leftStringType, *phpVersion; if (!zp::parse(execute_data, left, leftStringType, phpVersion)) RETURN_THROWS(); @@ -1557,47 +572,6 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) RETURN_BOOL(result == 1); }); - cls.method(sigs::resolveCommonMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_string *operator_; - zval *leftType, *rightType, *phpVersion; - if (!zp::parse(execute_data, operator_, leftType, rightType, phpVersion)) RETURN_THROWS(); - if (UNEXPECTED(!otArgType(leftType, 2) || !otArgType(rightType, 3) || !otArgPhpVersion(phpVersion, 4))) RETURN_THROWS(); - IetrBinaryKind kind; - if (UNEXPECTED(!otOperatorKind(operator_, kind))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::resolveCommonMath(kind, leftType, rightType, phpVersion)); - }); - - 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::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(); - if (UNEXPECTED(!otArgType(type, 1) || !otArgPhpVersion(phpVersion, 2))) RETURN_THROWS(); - PT_RETURN_VAL(OperatorTypeHelper::negate(type, phpVersion)); - }); - - cls.method(sigs::intDivide, OT_BINARY_GLUE(OperatorTypeHelper::intDivide(leftType, rightType))); - cls.shadow(&pt_ce_operator_type_helper); } diff --git a/turbo-ext/src/OversizedArrayType.cpp b/turbo-ext/src/OversizedArrayType.cpp index 5d751e8a366..aa82b7b4781 100644 --- a/turbo-ext/src/OversizedArrayType.cpp +++ b/turbo-ext/src/OversizedArrayType.cpp @@ -273,7 +273,7 @@ PT_MINIT_REGISTRATION(pt_register_oversized_array_type) ptdecl::OversizedArrayType::declareClass(cls); ptdecl::OversizedArrayType::declareProperties(cls); /* mirrors toValueSet() */ - cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS(false, true)); + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/StrictMixedType.cpp b/turbo-ext/src/StrictMixedType.cpp index 46e78aa5c2e..29b2e1c5695 100644 --- a/turbo-ext/src/StrictMixedType.cpp +++ b/turbo-ext/src/StrictMixedType.cpp @@ -436,6 +436,8 @@ PT_MINIT_REGISTRATION(pt_register_strict_mixed_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("ismixedallowed"), PT_CLASS_IDENTIFIER, PT_LC("mixed"))); }); + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createinvalid")); + /* the traits, in the twin's `use` order (UndecidedComparisonCompoundTypeTrait * brings UndecidedComparisonTypeTrait with it); the class body above wins * over every name it declares */ diff --git a/turbo-ext/src/StringType.cpp b/turbo-ext/src/StringType.cpp index 01aba3a6917..8d4553c56e0 100644 --- a/turbo-ext/src/StringType.cpp +++ b/turbo-ext/src/StringType.cpp @@ -316,12 +316,10 @@ class StringType static zv::Val getFiniteTypes() { return zv::Val(zv::Arr::empty()); } - /* ExponentiateHelper::exponentiate($this, $exponent) */ + /* $this->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion) */ 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); + return pt_value_set_power(self, exponent, phpVersion); } /* new IdentifierTypeNode('string') */ diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index 06e79bbb4a1..24f2b0647ec 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -176,6 +176,16 @@ void pt_value_set_of_this(INTERNAL_FUNCTION_PARAMETERS, const char *lcFactory, s PT_RETURN_VAL(pt_value_set_create(lcFactory, len, 1, ZEND_THIS)); } +zv::Val pt_value_set_power(zend_object *type, zval *exponent, zval *phpVersion) +{ + zv::Val baseValues = pt_type_call(type, PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(baseValues.isUndef())) return zv::Val(); + zv::Val exponentValues = pt_type_call(Z_OBJ_P(exponent), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(exponentValues.isUndef())) return zv::Val(); + zv::Args argv{exponentValues.raw(), phpVersion}; + return pt_type_call(Z_OBJ_P(baseValues.raw()), PT_LC("arithmeticpower"), 2, argv); +} + void pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); @@ -2309,14 +2319,8 @@ static void ZEND_FASTCALL arrayTraitChunkArray(INTERNAL_FUNCTION_PARAMETERS) void pt_type_trait_array(reg::Class &cls) { namespace sigs = ptdecl::ArrayTypeTrait::sig; - /* ValueSet::createArrays(!$this->isIterableAtLeastOnce()->yes(), !...->no()) */ - cls.traitMethod(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { - ZEND_PARSE_PARAMETERS_NONE(); - zend_long nonEmpty = pt_type_call_trinary(PT_THIS_OBJ, PT_LC("isiterableatleastonce"), 0, NULL); - if (UNEXPECTED(nonEmpty < 0)) RETURN_THROWS(); - zv::Args args{nonEmpty != PT_TRI_YES, nonEmpty != PT_TRI_NO}; - PT_RETURN_VAL(pt_value_set_create(PT_LC("createarrays"), 2, args)); - }); + /* ValueSet::createArrays($this) */ + cls.traitMethod(sigs::toValueSet, PT_VALUE_SET_OF_ARRAYS); cls.traitMethod(sigs::isArray, trinaryYes0); cls.traitOp(PT_OP_IS_ARRAY, PT_OP_LAMBDA { return pt_op_trinary(PT_TRI_YES); }); cls.traitMethod(sigs::toArray, this0); @@ -5490,6 +5494,35 @@ static void ZEND_FASTCALL operatorBinary(INTERNAL_FUNCTION_PARAMETERS) PT_RETURN_VAL(pt_operator_type_helper_binary(Op, ZEND_THIS, right, phpVersion)); } +/* $this->toValueSet()->($right->toValueSet(), $phpVersion) */ +static void operatorValueSet(INTERNAL_FUNCTION_PARAMETERS, const char *lcOperator, size_t len) +{ + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + zv::Val leftValues = pt_type_call(PT_THIS_OBJ, PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(leftValues.isUndef())) RETURN_THROWS(); + zv::Val rightValues = pt_type_call(Z_OBJ_P(right), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(rightValues.isUndef())) RETURN_THROWS(); + zv::Args argv{rightValues.raw(), phpVersion}; + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(leftValues.raw()), lcOperator, len, 2, argv)); +} + +#define PT_OPERATOR_VALUE_SET(lcOperator) \ + [](INTERNAL_FUNCTION_PARAMETERS) { operatorValueSet(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC(lcOperator)); } + +/* a bool method of a ValueSet, such as isEmpty(); -1 = pending exception */ +static int valueSetFlag(zval *valueSet, const char *lcMethod, size_t len) +{ + zv::Val flag = pt_type_call(Z_OBJ_P(valueSet), lcMethod, len, 0, NULL); + if (UNEXPECTED(flag.isUndef())) return -1; + return Z_TYPE_P(flag.raw()) == IS_TRUE ? 1 : 0; +} + +static int valueSetIsEmpty(zval *valueSet) +{ + return valueSetFlag(valueSet, PT_LC("isempty")); +} + void pt_type_trait_operator(reg::Class &cls) { namespace sigs = ptdecl::OperatorTypeTrait::sig; @@ -5509,22 +5542,89 @@ void pt_type_trait_operator(reg::Class &cls) RETURN_NULL(); }); cls.traitMethod(sigs::toValueSet, PT_VALUE_SET_CREATE("createunknown")); - 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); + /* the arrays of two array operands are unioned, the rest is up to their values */ + cls.traitMethod(sigs::arithmeticAdd, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + zv::Val leftValues = pt_type_call(PT_THIS_OBJ, PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(leftValues.isUndef())) RETURN_THROWS(); + zv::Val rightValues = pt_type_call(Z_OBJ_P(right), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(rightValues.isUndef())) RETURN_THROWS(); + int leftEmpty = valueSetIsEmpty(leftValues.raw()); + if (UNEXPECTED(leftEmpty < 0)) RETURN_THROWS(); + int rightEmpty = leftEmpty == 1 ? 1 : valueSetIsEmpty(rightValues.raw()); + if (UNEXPECTED(rightEmpty < 0)) RETURN_THROWS(); + zend_long leftIsArray = pt_type_call_trinary(PT_THIS_OBJ, PT_LC("isarray"), 0, NULL); + if (UNEXPECTED(leftIsArray < 0)) RETURN_THROWS(); + zend_long rightIsArray = pt_type_call_trinary(Z_OBJ_P(right), PT_LC("isarray"), 0, NULL); + if (UNEXPECTED(rightIsArray < 0)) RETURN_THROWS(); + if (leftEmpty == 0 && rightEmpty == 0 && leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_YES) { + zv::Args argv{right, phpVersion}; + PT_RETURN_VAL(pt_type_call(PT_THIS_OBJ, PT_LC("arrayunion"), 2, argv)); + } + /* strings, numbers, booleans and arrays added to each other give each of them, + * as the operand types themselves to keep what else they tell */ + if (leftIsArray == PT_TRI_MAYBE && rightIsArray == PT_TRI_MAYBE) { + int leftIsPlusable = valueSetFlag(leftValues.raw(), PT_LC("isplusable")); + if (UNEXPECTED(leftIsPlusable < 0)) RETURN_THROWS(); + int rightIsPlusable = valueSetFlag(rightValues.raw(), PT_LC("isplusable")); + if (UNEXPECTED(rightIsPlusable < 0)) RETURN_THROWS(); + if (leftIsPlusable == 1 && rightIsPlusable == 1) { + zval operands[2]; + ZVAL_OBJ(&operands[0], PT_THIS_OBJ); + ZVAL_COPY_VALUE(&operands[1], right); + PT_RETURN_VAL(pt_type_combinator_union(2, operands)); + } + if (leftIsPlusable == 1) { + int rightIsUnknown = valueSetFlag(rightValues.raw(), PT_LC("isunknown")); + if (UNEXPECTED(rightIsUnknown < 0)) RETURN_THROWS(); + if (rightIsUnknown == 1) { + RETURN_OBJ_COPY(PT_THIS_OBJ); + } + } + if (rightIsPlusable == 1) { + int leftIsUnknown = valueSetFlag(leftValues.raw(), PT_LC("isunknown")); + if (UNEXPECTED(leftIsUnknown < 0)) RETURN_THROWS(); + if (leftIsUnknown == 1) { + RETURN_COPY(right); + } + } + } + zv::Args argv{rightValues.raw(), phpVersion}; + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(leftValues.raw()), PT_LC("arithmeticadd"), 2, argv)); + }); + cls.traitMethod(sigs::arithmeticSubtract, PT_OPERATOR_VALUE_SET("arithmeticsubtract")); + cls.traitMethod(sigs::arithmeticMultiply, PT_OPERATOR_VALUE_SET("arithmeticmultiply")); + cls.traitMethod(sigs::arithmeticDivide, PT_OPERATOR_VALUE_SET("arithmeticdivide")); + /* intdiv() takes integers: the operands' integer values divide */ + cls.traitMethod(sigs::arithmeticIntDivide, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + zv::Val leftInteger = pt_type_call(PT_THIS_OBJ, PT_LC("tointeger"), 0, NULL); + if (UNEXPECTED(leftInteger.isUndef())) RETURN_THROWS(); + zv::Val leftValues = pt_type_call(Z_OBJ_P(leftInteger.raw()), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(leftValues.isUndef())) RETURN_THROWS(); + zv::Val rightInteger = pt_type_call(Z_OBJ_P(right), PT_LC("tointeger"), 0, NULL); + if (UNEXPECTED(rightInteger.isUndef())) RETURN_THROWS(); + zv::Val rightValues = pt_type_call(Z_OBJ_P(rightInteger.raw()), PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(rightValues.isUndef())) RETURN_THROWS(); + zv::Args argv{rightValues.raw(), phpVersion}; + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(leftValues.raw()), PT_LC("arithmeticintdivide"), 2, argv)); + }); + cls.traitMethod(sigs::arithmeticModulo, PT_OPERATOR_VALUE_SET("arithmeticmodulo")); + /* $this->toValueSet()->arithmeticNegate($phpVersion) */ 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)); + zv::Val valueSet = pt_type_call(PT_THIS_OBJ, PT_LC("tovalueset"), 0, NULL); + if (UNEXPECTED(valueSet.isUndef())) RETURN_THROWS(); + PT_RETURN_VAL(pt_type_call(Z_OBJ_P(valueSet.raw()), PT_LC("arithmeticnegate"), 1, 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::bitwiseAnd, PT_OPERATOR_VALUE_SET("bitwiseand")); + cls.traitMethod(sigs::bitwiseOr, PT_OPERATOR_VALUE_SET("bitwiseor")); + cls.traitMethod(sigs::bitwiseXor, PT_OPERATOR_VALUE_SET("bitwisexor")); + cls.traitMethod(sigs::bitwiseShiftLeft, PT_OPERATOR_VALUE_SET("bitwiseshiftleft")); + cls.traitMethod(sigs::bitwiseShiftRight, PT_OPERATOR_VALUE_SET("bitwiseshiftright")); cls.traitMethod(sigs::stringConcatenate, operatorBinary); cls.traitMethod(sigs::arrayUnion, operatorBinary); /* $this->toValueSet()->getPossiblyLooselyEqualType($phpVersion) */ diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index a19c98b0be8..9a217397a30 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -186,6 +186,10 @@ void pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS); * $this->getTypes() */ void pt_value_set_of_members(INTERNAL_FUNCTION_PARAMETERS, bool intersect); +/* $type->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); + * UNDEF = pending exception */ +zv::Val pt_value_set_power(zend_object *type, zval *exponent, zval *phpVersion); + #define PT_VALUE_SET_OF_THIS(factory) \ [](INTERNAL_FUNCTION_PARAMETERS) { pt_value_set_of_this(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC(factory)); } @@ -195,12 +199,7 @@ void pt_value_set_of_members(INTERNAL_FUNCTION_PARAMETERS, bool intersect); PT_RETURN_VAL(pt_value_set_create(PT_LC(factory))); \ } -#define PT_VALUE_SET_OF_ARRAYS(empty, nonEmpty) \ - [](INTERNAL_FUNCTION_PARAMETERS) { \ - ZEND_PARSE_PARAMETERS_NONE(); \ - zv::Args args_{(bool) (empty), (bool) (nonEmpty)}; \ - PT_RETURN_VAL(pt_value_set_create(PT_LC("createarrays"), 2, args_)); \ - } +#define PT_VALUE_SET_OF_ARRAYS PT_VALUE_SET_OF_THIS("createarrays") /* }}} */ diff --git a/turbo-ext/src/UnresolvedTemplateArgumentType.cpp b/turbo-ext/src/UnresolvedTemplateArgumentType.cpp index f1541e31ef6..2649d43acb6 100644 --- a/turbo-ext/src/UnresolvedTemplateArgumentType.cpp +++ b/turbo-ext/src/UnresolvedTemplateArgumentType.cpp @@ -683,6 +683,7 @@ PT_MINIT_REGISTRATION(pt_register_unresolved_template_argument_type) cls.method(sigs::getCallableParametersAcceptors, utaDelegate); cls.method(sigs::isCloneable, utaDelegate); cls.method(sigs::toBoolean, utaDelegate); + cls.method(sigs::toValueSet, utaDelegate); cls.method(sigs::toNumber, utaDelegate); cls.method(sigs::toBitwiseNotType, utaDelegate); cls.method(sigs::toGetClassResultType, utaDelegate); diff --git a/turbo-ext/src/VoidType.cpp b/turbo-ext/src/VoidType.cpp index 16c9d600c6a..ea3716f62e1 100644 --- a/turbo-ext/src/VoidType.cpp +++ b/turbo-ext/src/VoidType.cpp @@ -385,6 +385,8 @@ PT_MINIT_REGISTRATION(pt_register_void_type) PT_RETURN_VAL(pt_type_native_type_node(position, PT_LC("isvoidallowed"), PT_CLASS_IDENTIFIER, PT_LC("void"))); }); + cls.method(sigs::toValueSet, PT_VALUE_SET_CREATE("createinvalid")); + /* the traits, in the twin's `use` order; the class body above wins over * every name it declares */ ptdecl::VoidType::registerTraits(cls); diff --git a/turbo-ext/src/generated/BenevolentUnionType.h b/turbo-ext/src/generated/BenevolentUnionType.h index 196e687b237..4eedd8b539d 100644 --- a/turbo-ext/src/generated/BenevolentUnionType.h +++ b/turbo-ext/src/generated/BenevolentUnionType.h @@ -57,7 +57,9 @@ inline constexpr char strings[] = "cb\0" /* 436 */ "traverse\0" /* 439 */ "right\0" /* 448 */ - "traverseSimultaneously"; /* 454 */ + "traverseSimultaneously\0" /* 454 */ + "PHPStan\\Type\\ValueSet\0" /* 477 */ + "toValueSet"; /* 499 */ 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 */ @@ -88,6 +90,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(448, 0, 45), /* traverseSimultaneously $right */ reg::packed(436, MAY_BE_CALLABLE), /* traverseSimultaneously $cb */ reg::packed(44, 0, 45), /* traverseSimultaneously return */ + reg::packed(44, 0, 477), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -107,6 +110,7 @@ inline constexpr sigtab::Sig inferTemplateTypes = { { 383 /* inferTemplateTypes inline constexpr sigtab::Sig inferTemplateTypesOn = { { 415 /* inferTemplateTypesOn */, 1, 22, 1, 23, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverse = { { 439 /* traverse */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig traverseSimultaneously = { { 454 /* traverseSimultaneously */, 2, 26, 2, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toValueSet = { { 499 /* toValueSet */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::BenevolentUnionType diff --git a/turbo-ext/src/generated/CallableType.h b/turbo-ext/src/generated/CallableType.h index 5e34c4a8c6b..5facf1d81ee 100644 --- a/turbo-ext/src/generated/CallableType.h +++ b/turbo-ext/src/generated/CallableType.h @@ -176,7 +176,9 @@ inline constexpr char strings[] = "position\0" /* 1952 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1961 */ "toNativePhpTypeNode\0" /* 1993 */ - "hasTemplateOrLateResolvableType"; /* 2013 */ + "hasTemplateOrLateResolvableType\0" /* 2013 */ + "PHPStan\\Type\\ValueSet\0" /* 2045 */ + "toValueSet"; /* 2067 */ 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 */ @@ -285,6 +287,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(233, 0, 1900), /* toPhpDocNode return */ reg::packed(1952, 0, 1961), /* toNativePhpTypeNode $position */ reg::packed(233, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(233, 0, 2045), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -368,6 +371,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 1885 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 2067 /* toValueSet */, 0, 107, 0, 107, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::CallableType diff --git a/turbo-ext/src/generated/IterableType.h b/turbo-ext/src/generated/IterableType.h index c808c2f0c0d..d4941d3c212 100644 --- a/turbo-ext/src/generated/IterableType.h +++ b/turbo-ext/src/generated/IterableType.h @@ -144,7 +144,9 @@ inline constexpr char strings[] = "position\0" /* 1581 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1590 */ "toNativePhpTypeNode\0" /* 1622 */ - "hasTemplateOrLateResolvableType"; /* 1642 */ + "hasTemplateOrLateResolvableType\0" /* 1642 */ + "PHPStan\\Type\\ValueSet\0" /* 1674 */ + "toValueSet"; /* 1696 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 8), /* __construct $keyType */ reg::packed(26, 0, 8), /* __construct $itemType */ @@ -241,6 +243,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(47, 0, 1529), /* toPhpDocNode return */ reg::packed(1581, 0, 1590), /* toNativePhpTypeNode $position */ reg::packed(47, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(47, 0, 1674), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -318,6 +321,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 1514 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1696 /* toValueSet */, 0, 95, 0, 95, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::IterableType diff --git a/turbo-ext/src/generated/MixedType.h b/turbo-ext/src/generated/MixedType.h index 8504500c181..256e9588343 100644 --- a/turbo-ext/src/generated/MixedType.h +++ b/turbo-ext/src/generated/MixedType.h @@ -228,7 +228,9 @@ inline constexpr char strings[] = "position\0" /* 3178 */ "PHPStan\\Type\\NativeTypePosition\0" /* 3187 */ "toNativePhpTypeNode\0" /* 3219 */ - "hasTemplateOrLateResolvableType"; /* 3239 */ + "hasTemplateOrLateResolvableType\0" /* 3239 */ + "PHPStan\\Type\\ValueSet\0" /* 3271 */ + "toValueSet"; /* 3293 */ 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 */ @@ -439,6 +441,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(72, 0, 3126), /* toPhpDocNode return */ reg::packed(3178, 0, 3187), /* toNativePhpTypeNode $position */ reg::packed(72, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(72, 0, 3271), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -577,6 +580,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 3111 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 3293 /* toValueSet */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::MixedType diff --git a/turbo-ext/src/generated/NonexistentParentClassType.h b/turbo-ext/src/generated/NonexistentParentClassType.h index 63c2608ef12..4f5362cbdbc 100644 --- a/turbo-ext/src/generated/NonexistentParentClassType.h +++ b/turbo-ext/src/generated/NonexistentParentClassType.h @@ -117,7 +117,9 @@ inline constexpr char strings[] = "position\0" /* 1505 */ "PHPStan\\Type\\NativeTypePosition\0" /* 1514 */ "toNativePhpTypeNode\0" /* 1546 */ - "hasTemplateOrLateResolvableType"; /* 1566 */ + "hasTemplateOrLateResolvableType\0" /* 1566 */ + "PHPStan\\Type\\ValueSet\0" /* 1598 */ + "toValueSet"; /* 1620 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 6), /* describe $level */ reg::packed(34, MAY_BE_STRING), /* describe return */ @@ -200,6 +202,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(34, 0, 1453), /* toPhpDocNode return */ reg::packed(1505, 0, 1514), /* toNativePhpTypeNode $position */ reg::packed(34, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(34, 0, 1598), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -253,6 +256,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 1438 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1620 /* toValueSet */, 0, 81, 0, 81, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::NonexistentParentClassType diff --git a/turbo-ext/src/generated/NumberSet.h b/turbo-ext/src/generated/NumberSet.h index 7894ace86f3..31f14be0690 100644 --- a/turbo-ext/src/generated/NumberSet.h +++ b/turbo-ext/src/generated/NumberSet.h @@ -57,43 +57,54 @@ inline constexpr char strings[] = "bitwiseOr\0" /* 254 */ "bitwiseXor\0" /* 264 */ "negate\0" /* 275 */ - "shiftLeft\0" /* 282 */ - "shiftRight\0" /* 292 */ - "operator\0" /* 303 */ - "calculate\0" /* 312 */ - "hasAnyInteger\0" /* 322 */ - "countMembers\0" /* 336 */ - "isEmpty\0" /* 349 */ - "hasFloatRange\0" /* 357 */ - "toHull\0" /* 371 */ - "interval\0" /* 378 */ - "integerToFloatInterval\0" /* 387 */ - "splitIntegerDivisor\0" /* 410 */ - "left\0" /* 430 */ - "calculateIntegers\0" /* 435 */ - "value\0" /* 453 */ - "shift\0" /* 459 */ - "shiftLeftOverflows\0" /* 465 */ - "divideIntegers\0" /* 484 */ - "numerator\0" /* 499 */ - "divisor\0" /* 509 */ - "divideBound\0" /* 517 */ - "toIntBound\0" /* 529 */ - "bitwiseOrXor\0" /* 540 */ - "getNonNegativeIntegerBounds\0" /* 553 */ - "allBitsMask\0" /* 581 */ - "getIntegerHull\0" /* 593 */ - "divisorMin\0" /* 608 */ - "divisorMax\0" /* 619 */ - "getMaxModuloMagnitude\0" /* 630 */ - "intDivideIntervals\0" /* 652 */ - "intDivideBound\0" /* 671 */ - "calculateFloats\0" /* 686 */ - "calculateFloat\0" /* 702 */ - "bound\0" /* 717 */ - "min\0" /* 723 */ - "max\0" /* 727 */ - "getFloatBoundWithInnerNeighbour"; /* 731 */ + "exponent\0" /* 282 */ + "power\0" /* 291 */ + "shiftLeft\0" /* 297 */ + "shiftRight\0" /* 307 */ + "operator\0" /* 318 */ + "calculate\0" /* 327 */ + "hasAnyInteger\0" /* 337 */ + "countMembers\0" /* 351 */ + "isEmpty\0" /* 364 */ + "hasFloatRange\0" /* 372 */ + "toHull\0" /* 386 */ + "interval\0" /* 393 */ + "integerToFloatInterval\0" /* 402 */ + "splitIntegerDivisor\0" /* 425 */ + "left\0" /* 445 */ + "calculateIntegers\0" /* 450 */ + "getBoundedIntegerInterval\0" /* 468 */ + "baseMin\0" /* 494 */ + "baseMax\0" /* 502 */ + "exponentMin\0" /* 510 */ + "exponentMax\0" /* 522 */ + "powerIntegerIntervals\0" /* 534 */ + "powerIntegerInterval\0" /* 556 */ + "base\0" /* 577 */ + "integerPower\0" /* 582 */ + "value\0" /* 595 */ + "shift\0" /* 601 */ + "shiftLeftOverflows\0" /* 607 */ + "divideIntegers\0" /* 626 */ + "numerator\0" /* 641 */ + "divisor\0" /* 651 */ + "divideBound\0" /* 659 */ + "toIntBound\0" /* 671 */ + "bitwiseOrXor\0" /* 682 */ + "getNonNegativeIntegerBounds\0" /* 695 */ + "allBitsMask\0" /* 723 */ + "getIntegerHull\0" /* 735 */ + "divisorMin\0" /* 750 */ + "divisorMax\0" /* 761 */ + "getMaxModuloMagnitude\0" /* 772 */ + "intDivideIntervals\0" /* 794 */ + "intDivideBound\0" /* 813 */ + "calculateFloats\0" /* 828 */ + "calculateFloat\0" /* 844 */ + "bound\0" /* 859 */ + "min\0" /* 865 */ + "max\0" /* 869 */ + "getFloatBoundWithInnerNeighbour"; /* 873 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $integerIntervals */ reg::packed(17, MAY_BE_ARRAY), /* __construct $floatIntervals */ @@ -134,13 +145,16 @@ inline constexpr reg::PackedArg args[] = { reg::packed(48, 0, 179), /* bitwiseXor return */ reg::packed(145, 0, 156), /* negate $phpVersion */ reg::packed(48, 0, 179), /* negate return */ + reg::packed(282, 0, 100), /* power $exponent */ + reg::packed(145, 0, 156), /* power $phpVersion */ + reg::packed(48, MAY_BE_NULL, 179), /* power return */ reg::packed(139, 0, 100), /* shiftLeft $right */ reg::packed(145, 0, 156), /* shiftLeft $phpVersion */ reg::packed(48, 0, 179), /* shiftLeft return */ reg::packed(139, 0, 100), /* shiftRight $right */ reg::packed(145, 0, 156), /* shiftRight $phpVersion */ reg::packed(48, 0, 179), /* shiftRight return */ - reg::packed(303, MAY_BE_STRING), /* calculate $operator */ + reg::packed(318, MAY_BE_STRING), /* calculate $operator */ reg::packed(139, 0, 100), /* calculate $right */ reg::packed(145, 0, 156), /* calculate $phpVersion */ reg::packed(48, 0, 179), /* calculate return */ @@ -149,53 +163,66 @@ inline constexpr reg::PackedArg args[] = { reg::packed(48, MAY_BE_BOOL), /* isEmpty return */ reg::packed(48, MAY_BE_BOOL), /* hasFloatRange return */ reg::packed(48, 0, 100), /* toHull return */ - reg::packed(378, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ + reg::packed(393, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ reg::packed(48, MAY_BE_ARRAY), /* integerToFloatInterval return */ - reg::packed(378, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ + reg::packed(393, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ reg::packed(48, MAY_BE_ARRAY), /* splitIntegerDivisor return */ - reg::packed(303, MAY_BE_STRING), /* calculateIntegers $operator */ - reg::packed(430, MAY_BE_ARRAY), /* calculateIntegers $left */ + reg::packed(318, MAY_BE_STRING), /* calculateIntegers $operator */ + reg::packed(445, MAY_BE_ARRAY), /* calculateIntegers $left */ reg::packed(139, MAY_BE_ARRAY), /* calculateIntegers $right */ reg::packed(48, 0, 179), /* calculateIntegers return */ - reg::packed(453, MAY_BE_LONG), /* shiftLeftOverflows $value */ - reg::packed(459, MAY_BE_LONG), /* shiftLeftOverflows $shift */ + reg::packed(48, MAY_BE_NULL | MAY_BE_ARRAY), /* getBoundedIntegerInterval return */ + reg::packed(494, MAY_BE_NULL | MAY_BE_LONG), /* powerIntegerIntervals $baseMin */ + reg::packed(502, MAY_BE_NULL | MAY_BE_LONG), /* powerIntegerIntervals $baseMax */ + reg::packed(510, MAY_BE_NULL | MAY_BE_LONG), /* powerIntegerIntervals $exponentMin */ + reg::packed(522, MAY_BE_NULL | MAY_BE_LONG), /* powerIntegerIntervals $exponentMax */ + reg::packed(48, MAY_BE_NULL, 179), /* powerIntegerIntervals return */ + reg::packed(494, MAY_BE_NULL | MAY_BE_LONG), /* powerIntegerInterval $baseMin */ + reg::packed(502, MAY_BE_NULL | MAY_BE_LONG), /* powerIntegerInterval $baseMax */ + reg::packed(282, MAY_BE_LONG), /* powerIntegerInterval $exponent */ + reg::packed(48, MAY_BE_NULL, 179), /* powerIntegerInterval return */ + reg::packed(577, MAY_BE_LONG), /* integerPower $base */ + reg::packed(282, MAY_BE_LONG), /* integerPower $exponent */ + reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* integerPower return */ + reg::packed(595, MAY_BE_LONG), /* shiftLeftOverflows $value */ + reg::packed(601, MAY_BE_LONG), /* shiftLeftOverflows $shift */ reg::packed(48, MAY_BE_BOOL), /* shiftLeftOverflows return */ - reg::packed(430, MAY_BE_ARRAY), /* divideIntegers $left */ + reg::packed(445, MAY_BE_ARRAY), /* divideIntegers $left */ reg::packed(139, MAY_BE_ARRAY), /* divideIntegers $right */ reg::packed(145, 0, 156), /* divideIntegers $phpVersion */ reg::packed(48, MAY_BE_ARRAY), /* divideIntegers return */ - reg::packed(499, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ - reg::packed(509, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ + reg::packed(641, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ + reg::packed(651, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $divisor */ reg::packed(48, MAY_BE_ARRAY), /* divideBound return */ - reg::packed(453, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ + reg::packed(595, MAY_BE_LONG | MAY_BE_DOUBLE), /* toIntBound $value */ reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* toIntBound return */ reg::packed(139, 0, 100), /* bitwiseOrXor $right */ reg::packed(48, 0, 179), /* bitwiseOrXor return */ reg::packed(48, MAY_BE_NULL | MAY_BE_ARRAY), /* getNonNegativeIntegerBounds return */ - reg::packed(453, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(595, MAY_BE_LONG), /* allBitsMask $value */ reg::packed(48, MAY_BE_LONG), /* allBitsMask return */ reg::packed(48, MAY_BE_ARRAY), /* getIntegerHull return */ - reg::packed(608, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ - reg::packed(619, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ + reg::packed(750, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMin */ + reg::packed(761, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude $divisorMax */ reg::packed(48, MAY_BE_NULL | MAY_BE_LONG), /* getMaxModuloMagnitude return */ - reg::packed(499, MAY_BE_ARRAY), /* intDivideIntervals $numerator */ - reg::packed(509, MAY_BE_ARRAY), /* intDivideIntervals $divisor */ + reg::packed(641, MAY_BE_ARRAY), /* intDivideIntervals $numerator */ + reg::packed(651, MAY_BE_ARRAY), /* intDivideIntervals $divisor */ reg::packed(48, 0, 179), /* intDivideIntervals return */ - reg::packed(499, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound $numerator */ - reg::packed(509, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound $divisor */ + reg::packed(641, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound $numerator */ + reg::packed(651, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound $divisor */ reg::packed(48, MAY_BE_LONG | MAY_BE_DOUBLE), /* intDivideBound return */ - reg::packed(303, MAY_BE_STRING), /* calculateFloats $operator */ - reg::packed(430, MAY_BE_ARRAY), /* calculateFloats $left */ + reg::packed(318, MAY_BE_STRING), /* calculateFloats $operator */ + reg::packed(445, MAY_BE_ARRAY), /* calculateFloats $left */ reg::packed(139, MAY_BE_ARRAY), /* calculateFloats $right */ reg::packed(145, 0, 156), /* calculateFloats $phpVersion */ reg::packed(48, 0, 179), /* calculateFloats return */ - reg::packed(303, MAY_BE_STRING), /* calculateFloat $operator */ - reg::packed(430, MAY_BE_DOUBLE), /* calculateFloat $left */ + reg::packed(318, MAY_BE_STRING), /* calculateFloat $operator */ + reg::packed(445, MAY_BE_DOUBLE), /* calculateFloat $left */ reg::packed(139, MAY_BE_DOUBLE), /* calculateFloat $right */ reg::packed(48, MAY_BE_DOUBLE), /* calculateFloat return */ - reg::packed(717, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ - reg::packed(723, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ - reg::packed(727, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ + reg::packed(859, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ + reg::packed(865, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ + reg::packed(869, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $max */ reg::packed(48, MAY_BE_ARRAY), /* getFloatBoundWithInnerNeighbour return */ }; using Sig = reg::Sig; @@ -219,31 +246,36 @@ inline constexpr sigtab::Sig bitwiseAnd = { { 243 /* bitwiseAnd */, 2, 28, 2, 30 inline constexpr sigtab::Sig bitwiseOr = { { 254 /* bitwiseOr */, 2, 31, 2, 33, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig bitwiseXor = { { 264 /* bitwiseXor */, 2, 34, 2, 36, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig negate = { { 275 /* negate */, 1, 37, 1, 38, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 282 /* shiftLeft */, 2, 39, 2, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shiftRight = { { 292 /* shiftRight */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig calculate = { { 312 /* calculate */, 3, 45, 3, 48, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasAnyInteger = { { 322 /* hasAnyInteger */, 0, 49, 0, 49, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig countMembers = { { 336 /* countMembers */, 0, 50, 0, 50, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isEmpty = { { 349 /* isEmpty */, 0, 51, 0, 51, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasFloatRange = { { 357 /* hasFloatRange */, 0, 52, 0, 52, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toHull = { { 371 /* toHull */, 0, 53, 0, 53, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig integerToFloatInterval = { { 387 /* integerToFloatInterval */, 1, 54, 1, 55, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig splitIntegerDivisor = { { 410 /* splitIntegerDivisor */, 1, 56, 1, 57, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateIntegers = { { 435 /* calculateIntegers */, 3, 58, 3, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeftOverflows = { { 465 /* shiftLeftOverflows */, 2, 62, 2, 64, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntegers = { { 484 /* divideIntegers */, 3, 65, 3, 68, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBound = { { 517 /* divideBound */, 2, 69, 2, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntBound = { { 529 /* toIntBound */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig bitwiseOrXor = { { 540 /* bitwiseOrXor */, 1, 74, 1, 75, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 553 /* getNonNegativeIntegerBounds */, 0, 76, 0, 76, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig allBitsMask = { { 581 /* allBitsMask */, 1, 77, 1, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerHull = { { 593 /* getIntegerHull */, 0, 79, 0, 79, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 630 /* getMaxModuloMagnitude */, 2, 80, 2, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivideIntervals = { { 652 /* intDivideIntervals */, 2, 83, 2, 85, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivideBound = { { 671 /* intDivideBound */, 2, 86, 2, 88, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloats = { { 686 /* calculateFloats */, 4, 89, 4, 93, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig calculateFloat = { { 702 /* calculateFloat */, 3, 94, 3, 97, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 731 /* getFloatBoundWithInnerNeighbour */, 3, 98, 3, 101, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig power = { { 291 /* power */, 2, 39, 2, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftLeft = { { 297 /* shiftLeft */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shiftRight = { { 307 /* shiftRight */, 2, 45, 2, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculate = { { 327 /* calculate */, 3, 48, 3, 51, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasAnyInteger = { { 337 /* hasAnyInteger */, 0, 52, 0, 52, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig countMembers = { { 351 /* countMembers */, 0, 53, 0, 53, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isEmpty = { { 364 /* isEmpty */, 0, 54, 0, 54, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasFloatRange = { { 372 /* hasFloatRange */, 0, 55, 0, 55, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toHull = { { 386 /* toHull */, 0, 56, 0, 56, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig integerToFloatInterval = { { 402 /* integerToFloatInterval */, 1, 57, 1, 58, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig splitIntegerDivisor = { { 425 /* splitIntegerDivisor */, 1, 59, 1, 60, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateIntegers = { { 450 /* calculateIntegers */, 3, 61, 3, 64, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getBoundedIntegerInterval = { { 468 /* getBoundedIntegerInterval */, 0, 65, 0, 65, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig powerIntegerIntervals = { { 534 /* powerIntegerIntervals */, 4, 66, 4, 70, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig powerIntegerInterval = { { 556 /* powerIntegerInterval */, 3, 71, 3, 74, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig integerPower = { { 582 /* integerPower */, 2, 75, 2, 77, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig shiftLeftOverflows = { { 607 /* shiftLeftOverflows */, 2, 78, 2, 80, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideIntegers = { { 626 /* divideIntegers */, 3, 81, 3, 84, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig divideBound = { { 659 /* divideBound */, 2, 85, 2, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toIntBound = { { 671 /* toIntBound */, 1, 88, 1, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bitwiseOrXor = { { 682 /* bitwiseOrXor */, 1, 90, 1, 91, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 695 /* getNonNegativeIntegerBounds */, 0, 92, 0, 92, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig allBitsMask = { { 723 /* allBitsMask */, 1, 93, 1, 94, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerHull = { { 735 /* getIntegerHull */, 0, 95, 0, 95, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getMaxModuloMagnitude = { { 772 /* getMaxModuloMagnitude */, 2, 96, 2, 98, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivideIntervals = { { 794 /* intDivideIntervals */, 2, 99, 2, 101, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intDivideBound = { { 813 /* intDivideBound */, 2, 102, 2, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloats = { { 828 /* calculateFloats */, 4, 105, 4, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig calculateFloat = { { 844 /* calculateFloat */, 3, 110, 3, 113, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFloatBoundWithInnerNeighbour = { { 873 /* getFloatBoundWithInnerNeighbour */, 3, 114, 3, 117, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::NumberSet diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index f31ace694cc..87d06ec2e44 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -33,37 +33,8 @@ inline constexpr char strings[] = "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 */ - "type\0" /* 239 */ - "getIntegerIntervals\0" /* 244 */ - "add\0" /* 264 */ - "arrayUnion\0" /* 268 */ - "tryArrayUnion\0" /* 279 */ - "subtract\0" /* 293 */ - "multiply\0" /* 302 */ - "shiftLeft\0" /* 311 */ - "shiftRight\0" /* 321 */ - "optimizeScalarType\0" /* 332 */ - "operator\0" /* 351 */ - "resolveCommonMath\0" /* 360 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 378 */ - "toIntegerOperand\0" /* 410 */ - "value\0" /* 427 */ - "getTypeFromValue\0" /* 433 */ - "getNeverType\0" /* 450 */ - "negate\0" /* 463 */ - "numeratorType\0" /* 470 */ - "divisorType\0" /* 484 */ - "intDivide\0" /* 496 */ - "PHPStan\\Type\\NumberSet\0" /* 506 */ - "getIntegerNumberSet"; /* 529 */ + "arrayUnion\0" /* 124 */ + "tryArrayUnion"; /* 135 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* concat $left */ reg::packed(23, 0, 5), /* concat $right */ @@ -73,40 +44,6 @@ inline constexpr reg::PackedArg args[] = { 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(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(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(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(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(105, 0, 5), /* getDivTypeFromTypes $leftType */ - reg::packed(114, 0, 5), /* getDivTypeFromTypes $rightType */ - reg::packed(29, 0, 40), /* getDivTypeFromTypes $phpVersion */ - reg::packed(63, 0, 5), /* getDivTypeFromTypes return */ - reg::packed(105, 0, 5), /* modulo $leftType */ - reg::packed(114, 0, 5), /* modulo $rightType */ - reg::packed(29, 0, 40), /* modulo $phpVersion */ - reg::packed(63, 0, 5), /* modulo return */ - reg::packed(239, 0, 5), /* getIntegerIntervals $type */ - reg::packed(63, MAY_BE_NULL | MAY_BE_ARRAY), /* getIntegerIntervals return */ - reg::packed(105, 0, 5), /* add $leftType */ - reg::packed(114, 0, 5), /* add $rightType */ - reg::packed(29, 0, 40), /* add $phpVersion */ - reg::packed(63, 0, 5), /* add return */ reg::packed(105, 0, 5), /* arrayUnion $leftType */ reg::packed(114, 0, 5), /* arrayUnion $rightType */ reg::packed(29, 0, 40), /* arrayUnion $phpVersion */ @@ -114,45 +51,6 @@ inline constexpr reg::PackedArg args[] = { 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(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(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(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(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(239, 0, 5), /* optimizeScalarType $type */ - reg::packed(63, 0, 5), /* optimizeScalarType return */ - reg::packed(351, MAY_BE_STRING), /* resolveCommonMath $operator */ - reg::packed(105, 0, 5), /* resolveCommonMath $leftType */ - reg::packed(114, 0, 5), /* resolveCommonMath $rightType */ - reg::packed(29, 0, 40), /* resolveCommonMath $phpVersion */ - reg::packed(63, 0, 5), /* resolveCommonMath return */ - reg::packed(239, 0, 378), /* toIntegerOperand $type */ - reg::packed(63, MAY_BE_LONG), /* toIntegerOperand return */ - reg::packed(427, 0), /* getTypeFromValue $value */ - reg::packed(63, 0, 5), /* getTypeFromValue return */ - reg::packed(105, 0, 5), /* getNeverType $leftType */ - reg::packed(114, 0, 5), /* getNeverType $rightType */ - reg::packed(63, 0, 5), /* getNeverType return */ - reg::packed(239, 0, 5), /* negate $type */ - reg::packed(29, 0, 40), /* negate $phpVersion */ - reg::packed(63, 0, 5), /* negate return */ - reg::packed(470, 0, 5), /* intDivide $numeratorType */ - reg::packed(484, 0, 5), /* intDivide $divisorType */ - reg::packed(29, 0, 40), /* intDivide $phpVersion */ - reg::packed(63, 0, 5), /* intDivide return */ - reg::packed(239, 0, 5), /* getIntegerNumberSet $type */ - reg::packed(63, MAY_BE_NULL, 506), /* getIntegerNumberSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -161,29 +59,8 @@ using Sig = reg::Sig; namespace sig { inline constexpr sigtab::Sig concat = { { 64 /* concat */, 3, 0, 3, 3, ZEND_ACC_PUBLIC | 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 */, 3, 28, 3, 31, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig modulo = { { 232 /* modulo */, 3, 32, 3, 35, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerIntervals = { { 244 /* getIntegerIntervals */, 1, 36, 1, 37, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig add = { { 264 /* add */, 3, 38, 3, 41, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 268 /* arrayUnion */, 3, 42, 3, 45, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 279 /* tryArrayUnion */, 2, 46, 2, 48, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig subtract = { { 293 /* subtract */, 3, 49, 3, 52, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig multiply = { { 302 /* multiply */, 3, 53, 3, 56, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 311 /* shiftLeft */, 3, 57, 3, 60, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftRight = { { 321 /* shiftRight */, 3, 61, 3, 64, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 332 /* optimizeScalarType */, 1, 65, 1, 66, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 360 /* resolveCommonMath */, 4, 67, 4, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntegerOperand = { { 410 /* toIntegerOperand */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 433 /* getTypeFromValue */, 1, 74, 1, 75, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 450 /* getNeverType */, 2, 76, 2, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 463 /* negate */, 2, 79, 2, 81, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 496 /* intDivide */, 3, 82, 3, 85, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerNumberSet = { { 529 /* getIntegerNumberSet */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 124 /* arrayUnion */, 3, 8, 3, 11, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 135 /* tryArrayUnion */, 2, 12, 2, 14, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper diff --git a/turbo-ext/src/generated/StrictMixedType.h b/turbo-ext/src/generated/StrictMixedType.h index 26c8f4839f0..2695020c59e 100644 --- a/turbo-ext/src/generated/StrictMixedType.h +++ b/turbo-ext/src/generated/StrictMixedType.h @@ -174,7 +174,9 @@ inline constexpr char strings[] = "position\0" /* 2536 */ "PHPStan\\Type\\NativeTypePosition\0" /* 2545 */ "toNativePhpTypeNode\0" /* 2577 */ - "hasTemplateOrLateResolvableType"; /* 2597 */ + "hasTemplateOrLateResolvableType\0" /* 2597 */ + "PHPStan\\Type\\ValueSet\0" /* 2629 */ + "toValueSet"; /* 2651 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(0, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -325,6 +327,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 2484), /* toPhpDocNode return */ reg::packed(2536, 0, 2545), /* toNativePhpTypeNode $position */ reg::packed(0, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(0, 0, 2629), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -425,6 +428,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 2469 /* getFiniteTypes */, 0, 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 2651 /* toValueSet */, 0, 149, 0, 149, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StrictMixedType diff --git a/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h b/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h index 6b53eabc8c5..f0bd703bc06 100644 --- a/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h +++ b/turbo-ext/src/generated/UnresolvedTemplateArgumentType.h @@ -198,60 +198,62 @@ inline constexpr char strings[] = "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 */ + "PHPStan\\Type\\ValueSet\0" /* 2829 */ + "toValueSet\0" /* 2851 */ + "toBitwiseNotType\0" /* 2862 */ + "toGetClassResultType\0" /* 2879 */ + "reflectionProvider\0" /* 2900 */ + "PHPStan\\Reflection\\ReflectionProvider\0" /* 2919 */ + "toClassConstantType\0" /* 2957 */ + "PHPStan\\Type\\ClassNameToObjectTypeResult\0" /* 2977 */ + "toObjectTypeForInstanceofCheck\0" /* 3018 */ + "objectOrClassType\0" /* 3049 */ + "allowString\0" /* 3067 */ + "allowSameClass\0" /* 3079 */ + "toObjectTypeForIsACheck\0" /* 3094 */ + "toInteger\0" /* 3118 */ + "toFloat\0" /* 3128 */ + "toString\0" /* 3136 */ + "toArray\0" /* 3145 */ + "toArrayKey\0" /* 3153 */ + "isSmallerThan\0" /* 3164 */ + "isSmallerThanOrEqual\0" /* 3178 */ + "isConstantValue\0" /* 3199 */ + "isConstantScalarValue\0" /* 3215 */ + "getConstantScalarTypes\0" /* 3237 */ + "getConstantScalarValues\0" /* 3260 */ + "isNull\0" /* 3284 */ + "isTrue\0" /* 3291 */ + "isFalse\0" /* 3298 */ + "isBoolean\0" /* 3306 */ + "isFloat\0" /* 3316 */ + "isInteger\0" /* 3324 */ + "isString\0" /* 3334 */ + "isNumericString\0" /* 3343 */ + "isDecimalIntegerString\0" /* 3359 */ + "isNonEmptyString\0" /* 3382 */ + "isNonFalsyString\0" /* 3399 */ + "isLiteralString\0" /* 3416 */ + "isLowercaseString\0" /* 3432 */ + "isUppercaseString\0" /* 3450 */ + "isClassString\0" /* 3468 */ + "isVoid\0" /* 3482 */ + "isScalar\0" /* 3489 */ + "looseCompare\0" /* 3498 */ + "getSmallerType\0" /* 3511 */ + "getSmallerOrEqualType\0" /* 3526 */ + "getGreaterType\0" /* 3548 */ + "getGreaterOrEqualType\0" /* 3563 */ + "ancestorClassName\0" /* 3585 */ + "templateTypeName\0" /* 3603 */ + "getTemplateType\0" /* 3620 */ + "receivedType\0" /* 3636 */ + "PHPStan\\Type\\Generic\\TemplateTypeMap\0" /* 3649 */ + "inferTemplateTypes\0" /* 3686 */ + "positionVariance\0" /* 3705 */ + "PHPStan\\Type\\Generic\\TemplateTypeVariance\0" /* 3722 */ + "getReferencedTemplateTypes\0" /* 3764 */ + "toAbsoluteNumber"; /* 3791 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* __construct $site */ reg::packed(25, 0, 38), /* __construct $templateType */ @@ -437,15 +439,16 @@ inline constexpr reg::PackedArg args[] = { reg::packed(114, 0, 570), /* isCloneable return */ reg::packed(114, 0, 2785), /* toBoolean return */ reg::packed(114, 0, 84), /* toNumber return */ + reg::packed(114, 0, 2829), /* toValueSet return */ reg::packed(114, 0, 84), /* toBitwiseNotType return */ reg::packed(114, 0, 84), /* toGetClassResultType return */ - reg::packed(2867, 0, 2886), /* toClassConstantType $reflectionProvider */ + reg::packed(2900, 0, 2919), /* toClassConstantType $reflectionProvider */ reg::packed(114, 0, 84), /* toClassConstantType 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, 2977), /* toObjectTypeForInstanceofCheck return */ + reg::packed(3049, 0, 84), /* toObjectTypeForIsACheck $objectOrClassType */ + reg::packed(3067, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowString */ + reg::packed(3079, MAY_BE_BOOL), /* toObjectTypeForIsACheck $allowSameClass */ + reg::packed(114, 0, 2977), /* toObjectTypeForIsACheck return */ reg::packed(114, 0, 84), /* toInteger return */ reg::packed(114, 0, 84), /* toFloat return */ reg::packed(114, 0, 84), /* toString return */ @@ -489,12 +492,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(3552, MAY_BE_STRING), /* getTemplateType $ancestorClassName */ - reg::packed(3570, MAY_BE_STRING), /* getTemplateType $templateTypeName */ + reg::packed(3585, MAY_BE_STRING), /* getTemplateType $ancestorClassName */ + reg::packed(3603, MAY_BE_STRING), /* getTemplateType $templateTypeName */ reg::packed(114, 0, 84), /* getTemplateType return */ - reg::packed(3603, 0, 84), /* inferTemplateTypes $receivedType */ - reg::packed(114, 0, 3616), /* inferTemplateTypes return */ - reg::packed(3672, 0, 3689), /* getReferencedTemplateTypes $positionVariance */ + reg::packed(3636, 0, 84), /* inferTemplateTypes $receivedType */ + reg::packed(114, 0, 3649), /* inferTemplateTypes return */ + reg::packed(3705, 0, 3722), /* getReferencedTemplateTypes $positionVariance */ reg::packed(114, MAY_BE_ARRAY), /* getReferencedTemplateTypes return */ reg::packed(114, 0, 84), /* toAbsoluteNumber return */ }; @@ -609,48 +612,49 @@ inline constexpr sigtab::Sig getCallableParametersAcceptors = { { 2742 /* getCal 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 2851 /* toValueSet */, 0, 184, 0, 184, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toBitwiseNotType = { { 2862 /* toBitwiseNotType */, 0, 185, 0, 185, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toGetClassResultType = { { 2879 /* toGetClassResultType */, 0, 186, 0, 186, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toClassConstantType = { { 2957 /* toClassConstantType */, 1, 187, 1, 188, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toObjectTypeForInstanceofCheck = { { 3018 /* toObjectTypeForInstanceofCheck */, 0, 189, 0, 189, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toObjectTypeForIsACheck = { { 3094 /* toObjectTypeForIsACheck */, 3, 190, 3, 193, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toInteger = { { 3118 /* toInteger */, 0, 194, 0, 194, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toFloat = { { 3128 /* toFloat */, 0, 195, 0, 195, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toString = { { 3136 /* toString */, 0, 196, 0, 196, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArray = { { 3145 /* toArray */, 0, 197, 0, 197, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toArrayKey = { { 3153 /* toArrayKey */, 0, 198, 0, 198, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThan = { { 3164 /* isSmallerThan */, 2, 199, 2, 201, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSmallerThanOrEqual = { { 3178 /* isSmallerThanOrEqual */, 2, 202, 2, 204, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConstantValue = { { 3199 /* isConstantValue */, 0, 205, 0, 205, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConstantScalarValue = { { 3215 /* isConstantScalarValue */, 0, 206, 0, 206, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantScalarTypes = { { 3237 /* getConstantScalarTypes */, 0, 207, 0, 207, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantScalarValues = { { 3260 /* getConstantScalarValues */, 0, 208, 0, 208, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNull = { { 3284 /* isNull */, 0, 209, 0, 209, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isTrue = { { 3291 /* isTrue */, 0, 210, 0, 210, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFalse = { { 3298 /* isFalse */, 0, 211, 0, 211, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isBoolean = { { 3306 /* isBoolean */, 0, 212, 0, 212, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isFloat = { { 3316 /* isFloat */, 0, 213, 0, 213, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInteger = { { 3324 /* isInteger */, 0, 214, 0, 214, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isString = { { 3334 /* isString */, 0, 215, 0, 215, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNumericString = { { 3343 /* isNumericString */, 0, 216, 0, 216, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isDecimalIntegerString = { { 3359 /* isDecimalIntegerString */, 0, 217, 0, 217, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNonEmptyString = { { 3382 /* isNonEmptyString */, 0, 218, 0, 218, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isNonFalsyString = { { 3399 /* isNonFalsyString */, 0, 219, 0, 219, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isLiteralString = { { 3416 /* isLiteralString */, 0, 220, 0, 220, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isLowercaseString = { { 3432 /* isLowercaseString */, 0, 221, 0, 221, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUppercaseString = { { 3450 /* isUppercaseString */, 0, 222, 0, 222, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isClassString = { { 3468 /* isClassString */, 0, 223, 0, 223, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isVoid = { { 3482 /* isVoid */, 0, 224, 0, 224, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isScalar = { { 3489 /* isScalar */, 0, 225, 0, 225, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig looseCompare = { { 3498 /* looseCompare */, 2, 226, 2, 228, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerType = { { 3511 /* getSmallerType */, 1, 229, 1, 230, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSmallerOrEqualType = { { 3526 /* getSmallerOrEqualType */, 1, 231, 1, 232, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterType = { { 3548 /* getGreaterType */, 1, 233, 1, 234, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getGreaterOrEqualType = { { 3563 /* getGreaterOrEqualType */, 1, 235, 1, 236, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTemplateType = { { 3620 /* getTemplateType */, 2, 237, 2, 239, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig inferTemplateTypes = { { 3686 /* inferTemplateTypes */, 1, 240, 1, 241, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getReferencedTemplateTypes = { { 3764 /* getReferencedTemplateTypes */, 1, 242, 1, 243, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toAbsoluteNumber = { { 3791 /* toAbsoluteNumber */, 0, 244, 0, 244, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::UnresolvedTemplateArgumentType diff --git a/turbo-ext/src/generated/VoidType.h b/turbo-ext/src/generated/VoidType.h index 042fca75052..0569ebff3ee 100644 --- a/turbo-ext/src/generated/VoidType.h +++ b/turbo-ext/src/generated/VoidType.h @@ -104,7 +104,9 @@ inline constexpr char strings[] = "position\0" /* 965 */ "PHPStan\\Type\\NativeTypePosition\0" /* 974 */ "toNativePhpTypeNode\0" /* 1006 */ - "hasTemplateOrLateResolvableType"; /* 1026 */ + "hasTemplateOrLateResolvableType\0" /* 1026 */ + "PHPStan\\Type\\ValueSet\0" /* 1058 */ + "toValueSet"; /* 1080 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_ARRAY), /* getReferencedClasses return */ reg::packed(12, MAY_BE_ARRAY), /* getObjectClassNames return */ @@ -167,6 +169,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 913), /* toPhpDocNode return */ reg::packed(965, 0, 974), /* toNativePhpTypeNode $position */ reg::packed(12, MAY_BE_BOOL), /* hasTemplateOrLateResolvableType return */ + reg::packed(12, 0, 1058), /* toValueSet return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -222,6 +225,7 @@ inline constexpr sigtab::Sig getFiniteTypes = { { 898 /* getFiniteTypes */, 0, 5 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 } }; +inline constexpr sigtab::Sig toValueSet = { { 1080 /* toValueSet */, 0, 61, 0, 61, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::VoidType diff --git a/turbo-ext/src/support.cpp b/turbo-ext/src/support.cpp index 9cd2b81e824..7d8c72d5909 100644 --- a/turbo-ext/src/support.cpp +++ b/turbo-ext/src/support.cpp @@ -52,7 +52,6 @@ static const pt_class_template pt_class_templates[PT_CLASS_COUNT] = { /* PT_CLASS_CLASS_NAME_TO_OBJECT_TYPE_RESULT */ {"classNameToObjectTypeResult", "PHPStan\\Type\\ClassNameToObjectTypeResult"}, /* PT_CLASS_IDENTIFIER_TYPE_NODE */ {"identifierTypeNode", "PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode"}, /* PT_CLASS_LOOSE_COMPARISON_HELPER */ {"looseComparisonHelper", "PHPStan\\Type\\LooseComparisonHelper"}, - /* PT_CLASS_EXPONENTIATE_HELPER */ {"exponentiateHelper", "PHPStan\\Type\\ExponentiateHelper"}, /* PT_CLASS_COMPOUND_TYPE */ {"compoundType", "PHPStan\\Type\\CompoundType"}, /* PT_CLASS_CONSTANT_SCALAR_TYPE */ {"constantScalarType", "PHPStan\\Type\\ConstantScalarType"}, /* PT_CLASS_GENERIC_TYPE_NODE */ {"genericTypeNode", "PHPStan\\PhpDocParser\\Ast\\Type\\GenericTypeNode"}, diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index ba1eb89093f..640e2b1a9b6 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -138,7 +138,6 @@ enum { PT_CLASS_CLASS_NAME_TO_OBJECT_TYPE_RESULT, PT_CLASS_IDENTIFIER_TYPE_NODE, PT_CLASS_LOOSE_COMPARISON_HELPER, - PT_CLASS_EXPONENTIATE_HELPER, PT_CLASS_COMPOUND_TYPE, PT_CLASS_CONSTANT_SCALAR_TYPE, PT_CLASS_GENERIC_TYPE_NODE, @@ -4100,28 +4099,15 @@ extern zend_class_entry *pt_ce_operator_type_helper; * 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 */ +/* the operators OperatorTypeTrait still hands to OperatorTypeHelper */ 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 */ +/* OperatorTypeHelper::concat() / arrayUnion()($left, $right, $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); /* }}} */ diff --git a/turbo-ext/tests/initializer-escape.php b/turbo-ext/tests/initializer-escape.php index e0366bcb3ba..52a3d309214 100644 --- a/turbo-ext/tests/initializer-escape.php +++ b/turbo-ext/tests/initializer-escape.php @@ -111,9 +111,13 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC // their union mixes both families, which the prefix does not let // through either (the native int's arithmeticSubtract() meets the twin's // 3 in OperatorTypeHelper) - the type callback was kept before that + // the native operators compute on the PHP ValueSet, which the prefix + // hands the twins' NumberSet and MixedType (UnaryMinusHandler, + // BinaryOpHandler) - the type callback was kept before that too check( ($label === 'ClassConstFetchHandler' && str_contains($e->getMessage(), 'PHPStanTurbo\\Dependencies')) - || ($label === 'BinaryOpHandler' && (str_contains($e->getMessage(), 'PHPStanTurbo\\TrinaryLogic returned') || str_contains($e->getMessage(), 'PHPStanTurbo\\VerbosityLevel given'))), + || ($label === 'BinaryOpHandler' && (str_contains($e->getMessage(), 'PHPStanTurbo\\TrinaryLogic returned') || str_contains($e->getMessage(), 'PHPStanTurbo\\VerbosityLevel given'))) + || (in_array($label, ['UnaryMinusHandler', 'BinaryOpHandler'], true) && str_contains($e->getMessage(), 'PHPStanTurbo\\NumberSet given')), "escaping getTypeCallback ($label): " . get_class($e) . ': ' . $e->getMessage(), ); } catch (\Throwable $e) { @@ -131,7 +135,16 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC $ieVarResult = $ieNodeScopeResolver->processExprNode($ieStmt, $ieVarExpr, $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()); foreach ([true, false] as $increment) { $ieTypeCallback = (new \PHPStanTurbo\IncDecTypeHelper($ieKeeping))->getTypeCallback($ieVarExpr, $ieVarResult, $increment); - check(is_string($ieDescribe($ieTypeCallback(false))) && is_string($ieDescribe($ieTypeCallback(true))), 'escaping getTypeCallback (IncDecTypeHelper): the callback resolves'); + foreach ([false, true] as $native) { + try { + check(is_string($ieDescribe($ieTypeCallback($native))), 'escaping getTypeCallback (IncDecTypeHelper): the callback resolves'); + } catch (\TypeError $e) { + // the native int computes `$a + 1` on the PHP ValueSet, which the + // prefix hands the twin's NumberSet - the type callback was kept + // before that + check(str_contains($e->getMessage(), 'PHPStanTurbo\\NumberSet given'), 'escaping getTypeCallback (IncDecTypeHelper): ' . $e->getMessage()); + } + } } } finally { $ieScope->popExpressionResultStorage(); @@ -147,7 +160,8 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC unset($storage, $ieVarResult, $ieTypeCallback); gc_collect_cycles(); $ieDeepStack(200); -check(count($ieKeeping->kept) === 15, 'escaping getTypeCallback: the keeping resolver was handed the callbacks (' . count($ieKeeping->kept) . ')'); +// UnaryMinusHandler stops at the PHP ValueSet before it asks for the native type +check(count($ieKeeping->kept) === 14, 'escaping getTypeCallback: the keeping resolver was handed the callbacks (' . count($ieKeeping->kept) . ')'); foreach ($ieKeeping->kept as $i => [$method, $callback, $asked, $answered]) { $ieDeepStack(50); try { @@ -201,14 +215,20 @@ public function callUnaryOperatorTypeSpecifyingExtensions(string $operatorSigil, $ieOversized = new \PhpParser\Node\Expr\Array_(array_map(static fn (int $i) => new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Scalar\Int_($i)), range(0, 300))); $ieContext = \PHPStan\Reflection\InitializerExprContext::fromClass(\PHPStan\TrinaryLogic::class, null); $ieResolver->getType($ieOversized, $ieContext); -$ieResolver->getType(new \PhpParser\Node\Expr\BinaryOp\Plus(new \PhpParser\Node\Scalar\Int_(1), new \PhpParser\Node\Expr\UnaryMinus($ieOversized)), $ieContext); +try { + $ieResolver->getType(new \PhpParser\Node\Expr\BinaryOp\Plus(new \PhpParser\Node\Scalar\Int_(1), new \PhpParser\Node\Expr\UnaryMinus($ieOversized)), $ieContext); +} catch (\TypeError $e) { + // the negation computes on the PHP ValueSet, which the prefix hands the + // twin's MixedType - the builder kept the callable before that + check(str_contains($e->getMessage(), 'PHPStanTurbo\\MixedType given'), 'escaping getTypeCallback (OversizedArrayBuilder): ' . $e->getMessage()); +} $iePhpCallable = static fn (\PhpParser\Node\Expr $expr): \PHPStan\Type\Type => new \PHPStanTurbo\IntegerType(); $ieResolver->getArrayType($ieOversized, $iePhpCallable); check(count($ieBuilder->kept) === 3 && $ieBuilder->kept[2] === $iePhpCallable, 'escaping getTypeCallback (OversizedArrayBuilder): the builder was handed the callables, the PHP one itself'); $ieAsked = [ new \PhpParser\Node\Scalar\Int_(7), new \PhpParser\Node\Scalar\MagicConst\Class_(), - new \PhpParser\Node\Expr\BinaryOp\Mul(new \PhpParser\Node\Scalar\Int_(6), new \PhpParser\Node\Scalar\Int_(7)), + new \PhpParser\Node\Expr\BinaryOp\Concat(new \PhpParser\Node\Scalar\Int_(6), new \PhpParser\Node\Scalar\Int_(7)), new \PhpParser\Node\Expr\Array_([new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Scalar\String_('x'), new \PhpParser\Node\Scalar\String_('k'))]), ]; $ieExpected = array_map(static fn ($expr) => $ieDescribe($ieResolver->getType($expr, $ieContext)), $ieAsked); diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index 56e6a690784..395d7c97396 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -999,7 +999,7 @@ public function getValue() $r['union full range and nan'] = $view(\PHPStan\Type\TypeCombinator::union($floatRangeClass::fromInterval(-INF, 0.0), new \PHPStan\Type\Constant\ConstantFloatType(NAN), $floatRangeClass::fromInterval(5e-324, INF))); $r['remove range from float'] = $view(\PHPStan\Type\TypeCombinator::remove(new \PHPStan\Type\FloatType(), $floatRangeClass::fromInterval(-PHP_FLOAT_MAX, PHP_FLOAT_MAX))); $r['remove from range'] = $view(\PHPStan\Type\TypeCombinator::remove($floatRangeClass::fromInterval(0.0, 10.0), new \PHPStan\Type\Constant\ConstantFloatType(5.5))); - // the interval arithmetic of OperatorTypeHelper::floatRangeMath() and its corners next to NAN + // the float interval arithmetic of NumberSet and its corners next to NAN $arithmeticOperands = [ 'range1-2' => $floatRangeClass::fromInterval(1.0, 2.0), 'range-3-4' => $floatRangeClass::fromInterval(-3.0, 4.0), @@ -7791,7 +7791,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(); +$observations['native PHPStan\Type\OperatorTypeHelper'] = (new ReflectionMethod(\PHPStan\Type\OperatorTypeHelper::class, 'concat'))->isInternal(); { $resolver = $stringContainer->getByType(\PHPStan\Reflection\InitializerExprTypeResolver::class); $r = []; @@ -7904,64 +7904,10 @@ 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 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); - $nodes = [ - '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)])]; $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)); - } - } - 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' : $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 () => $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 () => $staticPrivate('getIntegerBounds', $operand)); - } - foreach ($integerOperands as $name => $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)); - } - foreach ([[1, 1], [1, 62], [1, 63], [PHP_INT_MAX, 1], [-1, 63], [-5, 2], [5, -1], [5, 64]] as [$value, $shift]) { - $r["shiftLeftOverflows $value $shift"] = $catching(static fn () => $staticPrivate('shiftLeftOverflows', $value, $shift)); - } - foreach ([1.5, -1.5, INF, -INF, NAN, 1e30, -1e30, (float) PHP_INT_MAX, (float) PHP_INT_MIN, 0.0] as $i => $value) { - $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 () => $staticPrivate('getTypeFromValue', $value)); - } - // the operator methods of OperatorTypeTrait (their bodies are OperatorTypeHelper's) + // the operator methods of OperatorTypeTrait (computed on ValueSet) // 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) { From c19e43d58869667e4eb29ed285687ad66c5b6f9c Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 06:29:01 +0000 Subject: [PATCH 05/15] Shadow ValueSet with a native mirror The value set every Type::toValueSet() returns, with the arithmetic, bitwise and loose-equality computations on it, is implemented natively in ValueSet.cpp. LooseComparisonHelper stays PHP and is called from it. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/ValueSet.php | 4 +- turbo-ext/src/NumberSet.cpp | 5 + turbo-ext/src/TypeTraits.cpp | 5 - turbo-ext/src/TypeTraits.h | 4 +- turbo-ext/src/ValueSet.cpp | 2451 ++++++++++++++++++++++++ turbo-ext/src/generated/ValueSet.h | 493 +++++ turbo-ext/src/support.cpp | 1 - turbo-ext/src/support.h | 2 - turbo-ext/tests/initializer-escape.php | 34 +- turbo-ext/tests/smoke.php | 3 +- turbo-ext/tests/type-family.php | 17 + 11 files changed, 2980 insertions(+), 39 deletions(-) create mode 100644 turbo-ext/src/ValueSet.cpp create mode 100644 turbo-ext/src/generated/ValueSet.h diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php index 9a76b4d3fb7..210394ec81b 100644 --- a/src/Type/ValueSet.php +++ b/src/Type/ValueSet.php @@ -5,7 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\TrinaryLogic; -use PHPStan\Turbo\ReferencedByTurboExtension; +use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; @@ -42,7 +42,7 @@ * the other strings, objects are kept as a type, because their class decides how * they compare. A set of unknown values stands for a type that can hold anything. */ -#[ReferencedByTurboExtension(key: 'valueSet')] +#[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ValueSet.cpp')] final class ValueSet { diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp index 0ee41f98a64..9bf8bc0e709 100644 --- a/turbo-ext/src/NumberSet.cpp +++ b/turbo-ext/src/NumberSet.cpp @@ -1143,6 +1143,11 @@ zv::Val pt_number_set_calculate(phpstanturbo::ietr::IetrBinaryKind kind, const N return phpstanturbo::NumberSetMath::calculate(kind, left, right, phpVersion); } +zend_class_entry *pt_number_set_class() +{ + return pt_ce_number_set; +} + bool pt_number_set_read(zend_object *numberSet, NumberSetData &out) { return NumberSet(numberSet).read(out); diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index 24f2b0647ec..dc21af59043 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -165,11 +165,6 @@ zv::Val pt_type_call_static(int classIdx, const char *lcname, size_t len, uint32 return pt_type_call_fn(fn, NULL, ce, argc, argv); } -zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc, zval *argv) -{ - return pt_type_call_static(PT_CLASS_VALUE_SET, lcFactory, len, argc, argv); -} - void pt_value_set_of_this(INTERNAL_FUNCTION_PARAMETERS, const char *lcFactory, size_t len) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 9a217397a30..15b845fb991 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -170,7 +170,7 @@ zv::Val pt_type_call_static(int classIdx, const char *lcname, size_t len, uint32 * declares one; UNDEF = pending exception */ zv::Val pt_type_new(int classIdx, uint32_t argc, zval *argv); -/* {{{ Type::toValueSet() (ValueSet.php stays PHP) */ +/* {{{ Type::toValueSet() (ValueSet.cpp) */ /* ValueSet::(...$args); UNDEF = pending exception */ zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc = 0, zval *argv = NULL); @@ -338,6 +338,8 @@ struct NumberSetData } // namespace phpstanturbo +/* the NumberSet class entry (NumberSet.cpp) */ +zend_class_entry *pt_number_set_class(); /* new NumberSet(...) from the data; UNDEF = pending exception */ zv::Val pt_number_set_new(const phpstanturbo::NumberSetData &data); /* the state of a NumberSet instance appended to `out`; false = pending diff --git a/turbo-ext/src/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp new file mode 100644 index 00000000000..2141ffceee1 --- /dev/null +++ b/turbo-ext/src/ValueSet.cpp @@ -0,0 +1,2451 @@ +/* + * PHPStanTurbo\ValueSet — native implementation of PHPStan\Type\ValueSet. + * + * Declared as PHPStan\Type\ValueSet itself at activation: final, no parent, + * no interfaces. State is the twin's sixteen private typed properties, as + * declared slots, so the std object handlers do GC/clone. The twin's + * private constructor is not declared: the sets are made by the factories + * only, natively; an uninitialized slot reads as the twin's default. + * + * The twin's private methods live only here, in ValueSetOps, method for + * method. LooseComparisonHelper stays PHP: compareValues() and the + * numeric-string parsers are called there. + */ + +#include "TypeTraits.h" +#include "IetrSupport.h" +#include "generated/ValueSet.h" +#include "generated/NumberSet.h" + +#include +#include +#include + +namespace slots = ptdecl::ValueSet::slot; +namespace sigs = ptdecl::ValueSet::sig; +namespace numberSlots = ptdecl::NumberSet::slot; + +static zend_class_entry *pt_ce_value_set = nullptr; + +namespace phpstanturbo { + +using namespace phpstanturbo::ietr; + +inline constexpr Method mGetEnumCases{"getEnumCases"}; +inline constexpr Method mIsExplicit{"isExplicit"}; +inline constexpr Method mUnion{"union"}; +inline constexpr Method mIntersect{"intersect"}; +inline constexpr Method mPower{"power"}; + +/* the fields of a set being made, owned; UNDEF reads as the twin's null / [] */ +struct ValueSetFields +{ + bool unknown = false; + bool null_ = false; + bool false_ = false; + bool true_ = false; + zv::Val numbers; + zv::Val constantStrings; + zv::Val strings; + bool emptyArray = false; + bool nonEmptyArray = false; + zv::Val arrays; + zv::Val objects; + bool resources = false; + bool invalid = false; + bool benevolent = false; + zv::Val never; + zv::Val unknownType; +}; + +/* a borrowed view of a NumberSet's slots */ +class NumbersView +{ +public: + explicit NumbersView(zval *numberSet) : o(Z_OBJ_P(numberSet)) {} + + HashTable *integerIntervals() const { return table(numberSlots::integerIntervals); } + HashTable *floatIntervals() const { return table(numberSlots::floatIntervals); } + bool nan() const { return Z_TYPE_P(OBJ_PROP_NUM(o, numberSlots::nan)) == IS_TRUE; } + uint32_t integerCount() const { return zend_hash_num_elements(integerIntervals()); } + uint32_t floatCount() const { return zend_hash_num_elements(floatIntervals()); } + bool isEmpty() const { return integerCount() == 0 && floatCount() == 0 && !nan(); } + +private: + zend_object *o; + + HashTable *table(uint32_t slot) const + { + zval *value = OBJ_PROP_NUM(o, slot); + return Z_TYPE_P(value) == IS_ARRAY ? Z_ARRVAL_P(value) : (HashTable *) &zend_empty_array; + } +}; + +/* a borrowed view of a ValueSet's slots */ +class ValueSetView +{ +public: + explicit ValueSetView(zend_object *o) : o(o) {} + + zend_object *object() const { return o; } + bool unknown() const { return flag(slots::unknown); } + bool null_() const { return flag(slots::null); } + bool false_() const { return flag(slots::false_); } + bool true_() const { return flag(slots::true_); } + zval *numbers() const { return objectOrNull(slots::numbers); } + HashTable *constantStrings() const + { + zval *value = OBJ_PROP_NUM(o, slots::constantStrings); + return Z_TYPE_P(value) == IS_ARRAY ? Z_ARRVAL_P(value) : (HashTable *) &zend_empty_array; + } + bool hasConstantStrings() const { return zend_hash_num_elements(constantStrings()) > 0; } + zval *strings() const { return objectOrNull(slots::strings); } + bool emptyArray() const { return flag(slots::emptyArray); } + bool nonEmptyArray() const { return flag(slots::nonEmptyArray); } + zval *arrays() const { return objectOrNull(slots::arrays); } + zval *objects() const { return objectOrNull(slots::objects); } + bool resources() const { return flag(slots::resources); } + bool invalid() const { return flag(slots::invalid); } + bool benevolent() const { return flag(slots::benevolent); } + zval *never() const { return objectOrNull(slots::never); } + zval *unknownType() const { return objectOrNull(slots::unknownType); } + +private: + zend_object *o; + + bool flag(uint32_t slot) const { return Z_TYPE_P(OBJ_PROP_NUM(o, slot)) == IS_TRUE; } + zval *objectOrNull(uint32_t slot) const + { + zval *value = OBJ_PROP_NUM(o, slot); + return Z_TYPE_P(value) == IS_OBJECT ? value : nullptr; + } +}; + +static void writeFlag(zend_object *o, uint32_t slot, bool value) +{ + zval *target = OBJ_PROP_NUM(o, slot); + zval_ptr_dtor(target); + ZVAL_BOOL(target, value); + Z_PROP_FLAG_P(target) = 0; +} + +static void writeValue(zend_object *o, uint32_t slot, zv::Val &value, bool array) +{ + zval *target = OBJ_PROP_NUM(o, slot); + zval_ptr_dtor(target); + if (value.isUndef() || Z_TYPE_P(value.raw()) == IS_NULL) { + if (array) { + ZVAL_EMPTY_ARRAY(target); + } else { + ZVAL_NULL(target); + } + } else { + zval taken = value.take(); + ZVAL_COPY_VALUE(target, &taken); + } + Z_PROP_FLAG_P(target) = 0; +} + +/* new self(...$fields); UNDEF = pending exception */ +static zv::Val newValueSet(ValueSetFields &fields) +{ + zval object; + if (UNEXPECTED(object_init_ex(&object, pt_ce_value_set) != SUCCESS)) return zv::Val(); + zend_object *o = Z_OBJ(object); + writeFlag(o, slots::unknown, fields.unknown); + writeFlag(o, slots::null, fields.null_); + writeFlag(o, slots::false_, fields.false_); + writeFlag(o, slots::true_, fields.true_); + writeValue(o, slots::numbers, fields.numbers, false); + writeValue(o, slots::constantStrings, fields.constantStrings, true); + writeValue(o, slots::strings, fields.strings, false); + writeFlag(o, slots::emptyArray, fields.emptyArray); + writeFlag(o, slots::nonEmptyArray, fields.nonEmptyArray); + writeValue(o, slots::arrays, fields.arrays, false); + writeValue(o, slots::objects, fields.objects, false); + writeFlag(o, slots::resources, fields.resources); + writeFlag(o, slots::invalid, fields.invalid); + writeFlag(o, slots::benevolent, fields.benevolent); + writeValue(o, slots::never, fields.never, false); + writeValue(o, slots::unknownType, fields.unknownType, false); + return zv::Val::adopt(object); +} + +static zv::Val copyOrNull(zval *value) +{ + return value == nullptr ? zv::Val::null() : zv::Val::copyOf(zv::Ref(value)); +} + +/* an owned set, or the borrowed operand it stands for */ +struct SetRef +{ + zv::Val owned; + zend_object *object; + + ValueSetView view() const { return ValueSetView(object); } +}; + +/* Mirrors the private methods of the twin. */ +class ValueSetOps +{ +public: + static constexpr zend_long EXACT_FLOAT_INTEGER_LIMIT = 9007199254740992LL; + static constexpr zend_long EXACT_FLOAT_DIGITS = 17; + static constexpr zend_long SHORT_FLOAT_DIGITS = 15; + + enum Op : uint8_t + { + OP_PLUS, + OP_MINUS, + OP_MUL, + OP_DIV, + OP_MOD, + OP_POW, + OP_SHIFT_LEFT, + OP_SHIFT_RIGHT, + OP_AND, + OP_OR, + OP_XOR, + }; + + /* {{{ factories */ + + static zv::Val createUnknown(zval *type) + { + ValueSetFields f; + f.unknown = true; + f.unknownType = copyOrNull(type); + return newValueSet(f); + } + + static zv::Val createEmpty() + { + ValueSetFields f; + return newValueSet(f); + } + + static zv::Val createInvalid() + { + ValueSetFields f; + f.unknown = true; + f.invalid = true; + return newValueSet(f); + } + + static zv::Val createNever(zval *never) + { + ValueSetFields f; + f.never = zv::Val::copyOf(zv::Ref(never)); + return newValueSet(f); + } + + static zv::Val createNull() + { + ValueSetFields f; + f.null_ = true; + return newValueSet(f); + } + + static zv::Val createBooleans(bool falseValue, bool trueValue) + { + ValueSetFields f; + f.false_ = falseValue; + f.true_ = trueValue; + return newValueSet(f); + } + + static zv::Val createNumbers(zv::Val numbers) + { + ValueSetFields f; + f.numbers = std::move(numbers); + return newValueSet(f); + } + + static zv::Val createNumbersOf(const NumberSetData &data) + { + IETR_VAL(numbers, pt_number_set_new(data)); + return createNumbers(std::move(numbers)); + } + + static zv::Val createConstantString(zend_string *value) + { + ValueSetFields f; + zv::Arr constantStrings = zv::Arr::create(1); + constantStrings.push(zv::Val::string(value)); + f.constantStrings = std::move(constantStrings); + return newValueSet(f); + } + + static zv::Val createStrings(zval *strings) + { + ValueSetFields f; + f.strings = zv::Val::copyOf(zv::Ref(strings)); + return newValueSet(f); + } + + static zv::Val createArrays(zval *arrays) + { + zend_long isIterableAtLeastOnce = triOn(arrays, mIsIterableAtLeastOnce); + if (UNEXPECTED(isIterableAtLeastOnce < 0)) return zv::Val(); + ValueSetFields f; + f.emptyArray = isIterableAtLeastOnce != PT_TRI_YES; + f.nonEmptyArray = isIterableAtLeastOnce != PT_TRI_NO; + f.arrays = zv::Val::copyOf(zv::Ref(arrays)); + return newValueSet(f); + } + + static zv::Val createObjects(zval *objects) + { + ValueSetFields f; + f.objects = zv::Val::copyOf(zv::Ref(objects)); + return newValueSet(f); + } + + static zv::Val createResources() + { + ValueSetFields f; + f.resources = true; + return newValueSet(f); + } + + static zv::Val toBenevolent(zend_object *self) + { + zend_object *clone = self->handlers->clone_obj(self); + if (UNEXPECTED(EG(exception) != NULL)) { + if (clone != NULL) OBJ_RELEASE(clone); + return zv::Val(); + } + writeFlag(clone, slots::benevolent, true); + zval result; + ZVAL_OBJ(&result, clone); + return zv::Val::adopt(result); + } + + /* }}} */ + + /* {{{ queries */ + + static bool numbersEmpty(zval *numbers) + { + return numbers == nullptr || NumbersView(numbers).isEmpty(); + } + + static bool isEmpty(ValueSetView s) + { + return !s.unknown() + && !s.null_() + && !s.false_() + && !s.true_() + && numbersEmpty(s.numbers()) + && !s.hasConstantStrings() + && s.strings() == nullptr + && !s.emptyArray() + && !s.nonEmptyArray() + && s.objects() == nullptr + && !s.resources(); + } + + static bool isPlusable(ValueSetView s) + { + return !s.unknown() && !s.null_() && s.objects() == nullptr && !s.resources() && !isEmpty(s); + } + + static bool isUnknown(ValueSetView s) + { + return s.unknown() && !s.invalid(); + } + + static bool mayBeString(ValueSetView s) + { + return s.unknown() || s.hasConstantStrings() || s.strings() != nullptr; + } + + static bool mayBeArray(ValueSetView s) + { + return s.unknown() || s.emptyArray() || s.nonEmptyArray(); + } + + static bool mayBeNonArray(ValueSetView s) + { + return s.unknown() + || s.null_() + || s.false_() + || s.true_() + || s.numbers() != nullptr + || s.hasConstantStrings() + || s.strings() != nullptr + || s.objects() != nullptr + || s.resources(); + } + + static bool mayBeNonNumber(ValueSetView s) + { + return s.unknown() + || s.null_() + || s.false_() + || s.true_() + || s.hasConstantStrings() + || s.strings() != nullptr + || mayBeArray(s) + || s.objects() != nullptr + || s.resources(); + } + + static bool isOnlyOneIntegerInterval(ValueSetView s) + { + if (s.numbers() == nullptr) return false; + NumbersView n(s.numbers()); + return n.integerCount() == 1 && n.floatCount() == 0 && !n.nan() && !mayBeNonNumber(s); + } + + static bool isOnlyIntegers(ValueSetView s) + { + if (s.numbers() == nullptr) return false; + NumbersView n(s.numbers()); + return n.floatCount() == 0 && !n.nan() && !mayBeNonNumber(s); + } + + static bool isOnlyStrings(ValueSetView s) + { + return mayBeString(s) + && !s.unknown() + && !s.null_() + && !s.false_() + && !s.true_() + && s.numbers() == nullptr + && !mayBeArray(s) + && s.objects() == nullptr + && !s.resources(); + } + + static bool isOnlyNonEmptyArrays(ValueSetView s) + { + return s.nonEmptyArray() && !s.emptyArray() && !mayBeNonArray(s); + } + + /* PT_TRI_*; -1 = pending exception */ + static zend_long isArray(ValueSetView s) + { + if (s.unknown()) { + if (s.unknownType() == nullptr) return PT_TRI_MAYBE; + return triOn(s.unknownType(), mIsArray); + } + if (mayBeArray(s) && mayBeNonArray(s)) return PT_TRI_MAYBE; + return mayBeArray(s) ? PT_TRI_YES : PT_TRI_NO; + } + + /* }}} */ + + /* {{{ union() / intersect() */ + + static zv::Val unionTypes(zval *left, zval *right) + { + if (left == nullptr || right == nullptr) return copyOrNull(left != nullptr ? left : right); + return union2(left, right); + } + + static bool containsString(HashTable *strings, zend_string *value) + { + zval *entry; + ZEND_HASH_FOREACH_VAL(strings, entry) { + if (Z_TYPE_P(entry) == IS_STRING && zend_string_equals(Z_STR_P(entry), value)) return true; + } ZEND_HASH_FOREACH_END(); + return false; + } + + static zv::Val union_(ValueSetView a, ValueSetView b) + { + if (a.unknown() || b.unknown()) { + ValueSetFields f; + f.unknown = true; + f.invalid = a.invalid() || b.invalid(); + f.benevolent = a.benevolent() || b.benevolent(); + if (a.unknown() && b.unknown() && a.unknownType() != nullptr && b.unknownType() != nullptr) { + f.unknownType = union2(a.unknownType(), b.unknownType()); + if (UNEXPECTED(f.unknownType.isUndef())) return zv::Val(); + } + return newValueSet(f); + } + + zv::Arr constantStrings = zv::Arr::copyOfTable(a.constantStrings()); + zval *entry; + ZEND_HASH_FOREACH_VAL(b.constantStrings(), entry) { + if (containsString(constantStrings.table(), Z_STR_P(entry))) continue; + constantStrings.push(zv::Ref(entry)); + } ZEND_HASH_FOREACH_END(); + + ValueSetFields f; + f.null_ = a.null_() || b.null_(); + f.false_ = a.false_() || b.false_(); + f.true_ = a.true_() || b.true_(); + if (a.numbers() == nullptr || b.numbers() == nullptr) { + f.numbers = copyOrNull(a.numbers() != nullptr ? a.numbers() : b.numbers()); + } else { + f.numbers = callOn(a.numbers(), mUnion, 1, b.numbers()); + if (UNEXPECTED(f.numbers.isUndef())) return zv::Val(); + } + f.constantStrings = std::move(constantStrings); + f.strings = unionTypes(a.strings(), b.strings()); + if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); + f.emptyArray = a.emptyArray() || b.emptyArray(); + f.nonEmptyArray = a.nonEmptyArray() || b.nonEmptyArray(); + f.arrays = unionTypes(a.arrays(), b.arrays()); + if (UNEXPECTED(f.arrays.isUndef())) return zv::Val(); + f.objects = unionTypes(a.objects(), b.objects()); + if (UNEXPECTED(f.objects.isUndef())) return zv::Val(); + f.resources = a.resources() || b.resources(); + f.benevolent = a.benevolent() || b.benevolent(); + f.never = copyOrNull(a.never() != nullptr ? a.never() : b.never()); + return newValueSet(f); + } + + static zv::Val intersectTypes(zval *left, zval *right) + { + if (left == nullptr || right == nullptr) return zv::Val::null(); + zv::Args argv{left, right}; + return pt_type_combinator_intersect(2, argv); + } + + static zv::Val intersect(ValueSetView a, ValueSetView b) + { + if (a.unknown()) return objectValue(b); + if (b.unknown()) return objectValue(a); + + zv::Arr constantStrings = zv::Arr::create(0); + ValueSetView pairs[2][2] = {{a, b}, {b, a}}; + for (auto &pair : pairs) { + ValueSetView set = pair[0]; + ValueSetView otherSet = pair[1]; + zval *entry; + ZEND_HASH_FOREACH_VAL(set.constantStrings(), entry) { + zend_string *constantString = Z_STR_P(entry); + if (containsString(constantStrings.table(), constantString)) continue; + if (!containsString(otherSet.constantStrings(), constantString)) { + if (otherSet.strings() == nullptr) continue; + IETR_VAL(constantType, newConstantStringType(constantString)); + zend_long isSuperType = triOn(otherSet.strings(), mIsSuperTypeOf, 1, constantType.raw()); + if (UNEXPECTED(isSuperType < 0)) return zv::Val(); + if (isSuperType == PT_TRI_NO) continue; + } + constantStrings.push(zv::Ref(entry)); + } ZEND_HASH_FOREACH_END(); + } + + ValueSetFields f; + f.null_ = a.null_() && b.null_(); + f.false_ = a.false_() && b.false_(); + f.true_ = a.true_() && b.true_(); + if (a.numbers() != nullptr && b.numbers() != nullptr) { + f.numbers = callOn(a.numbers(), mIntersect, 1, b.numbers()); + if (UNEXPECTED(f.numbers.isUndef())) return zv::Val(); + } + f.constantStrings = std::move(constantStrings); + f.strings = intersectTypes(a.strings(), b.strings()); + if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); + f.emptyArray = a.emptyArray() && b.emptyArray(); + f.nonEmptyArray = a.nonEmptyArray() && b.nonEmptyArray(); + f.arrays = intersectTypes(a.arrays(), b.arrays()); + if (UNEXPECTED(f.arrays.isUndef())) return zv::Val(); + f.objects = intersectTypes(a.objects(), b.objects()); + if (UNEXPECTED(f.objects.isUndef())) return zv::Val(); + f.resources = a.resources() && b.resources(); + f.benevolent = a.benevolent() || b.benevolent(); + f.never = copyOrNull(a.never() != nullptr ? a.never() : b.never()); + return newValueSet(f); + } + + /* }}} */ + + /* {{{ type construction */ + + static zv::Val objectWithoutClassType() + { + zval z; + return adopted(pt_object_without_class_type_new(&z), z); + } + + static zv::Val resourceType() + { + zval z; + return adopted(pt_resource_type_new(&z), z); + } + + static zv::Val emptyConstantArrayType() + { + zval keys, values, z; + ZVAL_EMPTY_ARRAY(&keys); + ZVAL_EMPTY_ARRAY(&values); + return adopted(pt_constant_array_type_new(&z, &keys, &values), z); + } + + static zv::Val mixedArrayType() + { + IETR_VAL(keyType, newMixedType()); + IETR_VAL(itemType, newMixedType()); + return newArrayType(keyType.raw(), itemType.raw()); + } + + static zv::Val nonEmptyArrayType() + { + zval z; + return adopted(pt_non_empty_array_type_new(&z), z); + } + + static zv::Val constantStringLiteral(const char *value, size_t len) + { + zend_string *string = zend_string_init(value, len, 0); + zv::Val type = newConstantStringType(string); + zend_string_release(string); + return type; + } + + /* createNumericStringType() */ + static zv::Val createNumericStringType() + { + IETR_VAL(stringType, newStringType()); + zval accessory; + if (UNEXPECTED(!pt_accessory_numeric_string_type_new(&accessory))) return zv::Val(); + zv::Arr types = zv::Arr::create(2); + types.push(std::move(stringType)); + types.push(zv::Val::adopt(accessory)); + return newIntersectionType(types); + } + + /* new BenevolentUnionType / UnionType of the types */ + static zv::Val newUnionOf(bool benevolent, std::initializer_list members) + { + zv::Arr types = zv::Arr::create((uint32_t) members.size()); + for (zv::Val *member : members) { + if (UNEXPECTED(member->isUndef())) return zv::Val(); + types.push(std::move(*member)); + } + return benevolent ? newBenevolentUnionType(types) : newUnionType(types); + } + + static zv::Val versionId(zval *phpVersion, zend_long &out) + { + zv::Val id = pt_php_version_get_version_id(phpVersion); + if (UNEXPECTED(id.isUndef())) return zv::Val(); + out = zval_get_long(id.raw()); + return id; + } + + /* createNumberLikeObjectType() */ + static zv::Val createNumberLikeObjectType(zval *phpVersion) + { + zend_long id; + if (UNEXPECTED(versionId(phpVersion, id).isUndef())) return zv::Val(); + zv::Arr types = zv::Arr::create(3); + IETR_VAL(gmp, newObjectTypeLiteral(PT_LC("GMP"))); + types.push(std::move(gmp)); + IETR_VAL(simpleXml, newObjectTypeLiteral(PT_LC("SimpleXMLElement"))); + types.push(std::move(simpleXml)); + if (id >= 80400) { + IETR_VAL(number, newObjectTypeLiteral(PT_LC("BcMath\\Number"))); + types.push(std::move(number)); + } + return unionOf(types); + } + + /* createStringableObjectType() */ + static zv::Val createStringableObjectType(zval *phpVersion) + { + zend_long id; + if (UNEXPECTED(versionId(phpVersion, id).isUndef())) return zv::Val(); + if (id < 80000) return objectWithoutClassType(); + IETR_VAL(stringable, newObjectTypeLiteral(PT_LC("Stringable"))); + IETR_VAL(gmp, newObjectTypeLiteral(PT_LC("GMP"))); + return union2(stringable.raw(), gmp.raw()); + } + + /* createTypeOfNumbers() */ + static zv::Val createTypeOfNumbers(const NumberSetData &numbers) + { + zv::Arr types = zv::Arr::create(4); + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + IETR_VAL(rangeType, fromInterval(interval.min, interval.max)); + types.push(std::move(rangeType)); + } + bool nan = numbers.nan; + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + if (nan && interval.min == -HUGE_VAL && interval.max == HUGE_VAL) { + IETR_VAL(floatType, newFloatType()); + types.push(std::move(floatType)); + nan = false; + continue; + } + IETR_VAL(floatType, interval.min == interval.max ? newConstantFloatType(interval.min) : pt_float_range_from_interval(interval.min, interval.max)); + types.push(std::move(floatType)); + } + if (nan) { + IETR_VAL(nanType, newConstantFloatType(std::numeric_limits::quiet_NaN())); + types.push(std::move(nanType)); + } + return unionOf(types); + } + + /* }}} */ + + /* {{{ numbers */ + + static bool readNumbers(zval *numberSet, NumberSetData &out) + { + return pt_number_set_read(Z_OBJ_P(numberSet), out); + } + + static NumberSetData anyInteger() + { + NumberSetData data; + data.integerIntervals.push_back({NullableLong::null(), NullableLong::null()}); + return data; + } + + static bool isOnlyFloatNumbers(const NumberSetData &numbers) + { + return numbers.integerIntervals.empty() && (!numbers.floatIntervals.empty() || numbers.nan); + } + + static bool isOnlyFloats(ValueSetView s) + { + if (s.numbers() == nullptr || mayBeNonNumber(s)) return false; + NumbersView n(s.numbers()); + return n.integerCount() == 0 && (n.floatCount() > 0 || n.nan()); + } + + static bool isOnlyInteger(const NumberSetData &numbers, zend_long value) + { + return numbers.integerIntervals.size() == 1 + && !numbers.integerIntervals[0].min.isNull + && !numbers.integerIntervals[0].max.isNull + && numbers.integerIntervals[0].min.value == value + && numbers.integerIntervals[0].max.value == value + && numbers.floatIntervals.empty() + && !numbers.nan; + } + + static bool sameIntegerInterval(const NumberSetData::IntegerInterval &a, const NumberSetData::IntegerInterval &b) + { + return a.min.isNull == b.min.isNull && (a.min.isNull || a.min.value == b.min.value) + && a.max.isNull == b.max.isNull && (a.max.isNull || a.max.value == b.max.value); + } + + /* $type->toNumberSet() read; has = false for null; false = pending exception */ + static bool typeNumbers(zval *type, NumberSetData &out, bool &has) + { + return pt_type_to_number_set(type, out, has); + } + + /* $type->toNumber()->toNumberSet() (or ->toInteger()) */ + static bool convertedNumbers(zval *type, const Method &conversion, NumberSetData &out, bool &has) + { + zv::Val converted = callOn(type, conversion); + if (UNEXPECTED(converted.isUndef())) return false; + return typeNumbers(converted.raw(), out, has); + } + + /* toNumberSetOfNumber(); has = false for null */ + static bool toNumberSetOfNumber(ValueSetView s, NumberSetData &out, bool &has) + { + has = false; + if (s.null_() || s.false_()) out.integerIntervals.push_back({NullableLong::of(0), NullableLong::of(0)}); + if (s.true_()) out.integerIntervals.push_back({NullableLong::of(1), NullableLong::of(1)}); + if (s.numbers() != nullptr && UNEXPECTED(!readNumbers(s.numbers(), out))) return false; + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + zv::Val constantType = newConstantStringType(Z_STR_P(entry)); + if (UNEXPECTED(constantType.isUndef())) return false; + bool hasNumbers; + if (UNEXPECTED(!convertedNumbers(constantType.raw(), mToNumber, out, hasNumbers))) return false; + if (!hasNumbers) return true; + } ZEND_HASH_FOREACH_END(); + if (s.strings() != nullptr) { + bool hasNumbers; + if (UNEXPECTED(!convertedNumbers(s.strings(), mToNumber, out, hasNumbers))) return false; + if (!hasNumbers) return true; + } + has = true; + return true; + } + + /* toIntegerNumbers() */ + static bool toIntegerNumbers(const NumberSetData &numbers, NumberSetData &out) + { + out.integerIntervals.insert(out.integerIntervals.end(), numbers.integerIntervals.begin(), numbers.integerIntervals.end()); + std::vector floatTypes; + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + zv::Val floatType = interval.min == interval.max ? newConstantFloatType(interval.min) : pt_float_range_from_interval(interval.min, interval.max); + if (UNEXPECTED(floatType.isUndef())) return false; + floatTypes.push_back(std::move(floatType)); + } + if (numbers.nan) { + zv::Val nanType = newConstantFloatType(std::numeric_limits::quiet_NaN()); + if (UNEXPECTED(nanType.isUndef())) return false; + floatTypes.push_back(std::move(nanType)); + } + for (zv::Val &floatType : floatTypes) { + NumberSetData integers; + bool has; + if (UNEXPECTED(!convertedNumbers(floatType.raw(), mToInteger, integers, has))) return false; + if (!has) integers = anyInteger(); + out.integerIntervals.insert(out.integerIntervals.end(), integers.integerIntervals.begin(), integers.integerIntervals.end()); + out.floatIntervals.insert(out.floatIntervals.end(), integers.floatIntervals.begin(), integers.floatIntervals.end()); + out.nan = out.nan || integers.nan; + } + return true; + } + + /* toOperandNumbers(); has = false for null */ + static bool toOperandNumbers(ValueSetView s, bool withoutArrays, bool integers, NumberSetData &out, bool &has) + { + has = false; + if (s.unknown()) { + out.integerIntervals.push_back({NullableLong::null(), NullableLong::null()}); + if (!integers) { + out.floatIntervals.push_back({-HUGE_VAL, HUGE_VAL}); + out.nan = true; + } + has = true; + return true; + } + if (((!withoutArrays && (s.emptyArray() || s.nonEmptyArray())) || s.resources()) && !s.benevolent()) return true; + + std::vector sets; + if (s.null_() || s.false_()) { + NumberSetData zero; + zero.integerIntervals.push_back({NullableLong::of(0), NullableLong::of(0)}); + sets.push_back(std::move(zero)); + } + if (s.true_()) { + NumberSetData one; + one.integerIntervals.push_back({NullableLong::of(1), NullableLong::of(1)}); + sets.push_back(std::move(one)); + } + if (s.numbers() != nullptr) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return false; + if (integers) { + NumberSetData integerNumbers; + if (UNEXPECTED(!toIntegerNumbers(numbers, integerNumbers))) return false; + sets.push_back(std::move(integerNumbers)); + } else { + sets.push_back(std::move(numbers)); + } + } + + std::vector types; + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + zv::Val constantType = newConstantStringType(Z_STR_P(entry)); + if (UNEXPECTED(constantType.isUndef())) return false; + types.push_back(std::move(constantType)); + } ZEND_HASH_FOREACH_END(); + if (s.strings() != nullptr) types.push_back(zv::Val::copyOf(zv::Ref(s.strings()))); + if (s.objects() != nullptr) types.push_back(zv::Val::copyOf(zv::Ref(s.objects()))); + for (zv::Val &type : types) { + NumberSetData numbers; + bool hasNumbers; + if (UNEXPECTED(!convertedNumbers(type.raw(), mToNumber, numbers, hasNumbers))) return false; + if (!hasNumbers) { + if (s.benevolent()) continue; + return true; + } + if (integers) { + NumberSetData integerNumbers; + bool hasIntegers; + if (UNEXPECTED(!convertedNumbers(type.raw(), mToInteger, integerNumbers, hasIntegers))) return false; + sets.push_back(hasIntegers ? std::move(integerNumbers) : anyInteger()); + } else { + sets.push_back(std::move(numbers)); + } + } + + if (sets.empty()) return true; + + // a value of two kinds that read as the same number is one member + for (const NumberSetData &set : sets) { + for (const NumberSetData::IntegerInterval &interval : set.integerIntervals) { + bool seen = false; + for (const NumberSetData::IntegerInterval &known : out.integerIntervals) { + if (sameIntegerInterval(known, interval)) { + seen = true; + break; + } + } + if (!seen) out.integerIntervals.push_back(interval); + } + for (const NumberSetData::FloatInterval &interval : set.floatIntervals) { + bool seen = false; + for (const NumberSetData::FloatInterval &known : out.floatIntervals) { + if (known.min == interval.min && known.max == interval.max) { + seen = true; + break; + } + } + if (!seen) out.floatIntervals.push_back(interval); + } + out.nan = out.nan || set.nan; + } + has = true; + return true; + } + + /* getIntegerNumbers(): the NumberSet itself, or nullptr */ + static zval *getIntegerNumbers(ValueSetView s) + { + zval *numbers = s.numbers(); + if (numbers == nullptr) return nullptr; + NumbersView n(numbers); + if (n.integerCount() == 0 || n.floatCount() > 0 || n.nan() || mayBeNonNumber(s)) return nullptr; + return numbers; + } + + /* }}} */ + + /* {{{ constants */ + + /* getConstantNumber(): IS_LONG / IS_DOUBLE, IS_NULL for none; UNDEF = pending exception */ + static zv::Val getConstantNumber(zval *numberType) + { + IETR_VAL(values, callOn(numberType, mGetConstantScalarValues)); + HashTable *ht = Z_ARRVAL_P(values.raw()); + if (zend_hash_num_elements(ht) != 1) return zv::Val::null(); + zval *value = zend_hash_index_find(ht, 0); + if (value == NULL || (Z_TYPE_P(value) != IS_LONG && Z_TYPE_P(value) != IS_DOUBLE)) return zv::Val::null(); + return zv::Val::copyOf(zv::Ref(value)); + } + + static zv::Val getConstantInteger(zval *integerType) + { + IETR_VAL(values, callOn(integerType, mGetConstantScalarValues)); + HashTable *ht = Z_ARRVAL_P(values.raw()); + if (zend_hash_num_elements(ht) != 1) return zv::Val::null(); + zval *value = zend_hash_index_find(ht, 0); + if (value == NULL || Z_TYPE_P(value) != IS_LONG) return zv::Val::null(); + return zv::Val::copyOf(zv::Ref(value)); + } + + /* in_array($number, [0, 0.0], true) */ + static bool isZero(zval *number) + { + return (Z_TYPE_P(number) == IS_LONG && Z_LVAL_P(number) == 0) || (Z_TYPE_P(number) == IS_DOUBLE && Z_DVAL_P(number) == 0.0); + } + + /* getFiniteScalarTypes(); finite = false for null; false = pending exception */ + static bool getFiniteScalarTypes(ValueSetView s, bool expandIntegerIntervals, std::vector &types, bool &finite) + { + finite = false; + if (s.unknown() || s.strings() != nullptr || s.emptyArray() || s.nonEmptyArray() || s.objects() != nullptr || s.resources()) return true; + + if (s.null_()) { + zv::Val nullType = newNullType(); + if (UNEXPECTED(nullType.isUndef())) return false; + types.push_back(std::move(nullType)); + } + if (s.false_()) { + zv::Val falseType = newConstantBooleanType(false); + if (UNEXPECTED(falseType.isUndef())) return false; + types.push_back(std::move(falseType)); + } + if (s.true_()) { + zv::Val trueType = newConstantBooleanType(true); + if (UNEXPECTED(trueType.isUndef())) return false; + types.push_back(std::move(trueType)); + } + if (s.numbers() != nullptr) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return false; + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + if (interval.min.isNull || interval.max.isNull) return true; + zend_long min = interval.min.value; + zend_long max = interval.max.value; + if (min != max) { + zend_long width; + bool overflows = __builtin_sub_overflow(max, min, &width); + // only a range on its own is listed, as IntegerRangeType::getFiniteTypes() lists it + if (!expandIntegerIntervals || overflows || width > PT_IETR_CALCULATE_SCALARS_LIMIT || !isOnlyOneIntegerInterval(s)) return true; + } + for (zend_long i = 0; i <= max - min; i++) { + zv::Val constantType = newConstantIntegerType(min + i); + if (UNEXPECTED(constantType.isUndef())) return false; + types.push_back(std::move(constantType)); + } + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + if (interval.min != interval.max) return true; + zv::Val constantType = newConstantFloatType(interval.min); + if (UNEXPECTED(constantType.isUndef())) return false; + types.push_back(std::move(constantType)); + } + if (numbers.nan) { + zv::Val nanType = newConstantFloatType(std::numeric_limits::quiet_NaN()); + if (UNEXPECTED(nanType.isUndef())) return false; + types.push_back(std::move(nanType)); + } + } + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + zv::Val constantType = newConstantStringType(Z_STR_P(entry)); + if (UNEXPECTED(constantType.isUndef())) return false; + types.push_back(std::move(constantType)); + } ZEND_HASH_FOREACH_END(); + + finite = true; + return true; + } + + /* is_numeric($value) of a string */ + static bool isNumeric(zend_string *value) + { + return is_numeric_string(ZSTR_VAL(value), ZSTR_LEN(value), NULL, NULL, 0) != 0; + } + + /* pow(): IS_NULL for an error; UNDEF = pending exception */ + static zv::Val pow(zval *base, zval *exponent) + { + if ((Z_TYPE_P(base) == IS_STRING && !isNumeric(Z_STR_P(base))) || (Z_TYPE_P(exponent) == IS_STRING && !isNumeric(Z_STR_P(exponent)))) { + return zv::Val::null(); + } + + // PHP 8.4 deprecates 0 ** -1, which still gives INF; the twin silences it with @ + int errorReporting = EG(error_reporting); + EG(error_reporting) &= (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE); + zv::Val result = phpOp(pow_function, base, exponent); + EG(error_reporting) = errorReporting; + return result; + } + + /* foldPower(): IS_NULL for an error */ + static zv::Val foldPower(zval *base, zval *exponent) + { + IETR_VAL(result, pow(base, exponent)); + if (Z_TYPE_P(result.raw()) == IS_NULL) return zv::Val::null(); + + IETR_VAL(resultType, typeFromValue(result.raw())); + + // a zero float stands for both zeros (0.0 === -0.0), and a negative odd exponent tells them apart + if (Z_TYPE_P(base) == IS_DOUBLE && Z_DVAL_P(base) == 0.0) { + zval otherBase; + ZVAL_DOUBLE(&otherBase, -Z_DVAL_P(base)); + IETR_VAL(otherZeroResult, pow(&otherBase, exponent)); + if (Z_TYPE_P(otherZeroResult.raw()) == IS_DOUBLE && !identical(otherZeroResult.raw(), result.raw())) { + IETR_VAL(otherType, newConstantFloatType(Z_DVAL_P(otherZeroResult.raw()))); + return union2(resultType.raw(), otherType.raw()); + } + } + + return resultType; + } + + /* foldValues(): IS_NULL for an error */ + static zv::Val foldValues(Op op, zval *left, zval *right) + { + if (op == OP_POW) { + IETR_VAL(leftValue, callOn(left, mGetValue)); + IETR_VAL(rightValue, callOn(right, mGetValue)); + return foldPower(leftValue.raw(), rightValue.raw()); + } + + if (op == OP_AND || op == OP_OR || op == OP_XOR) { + zend_long leftIsString = triOn(left, mIsString); + if (UNEXPECTED(leftIsString < 0)) return zv::Val(); + if (leftIsString == PT_TRI_YES) { + zend_long rightIsString = triOn(right, mIsString); + if (UNEXPECTED(rightIsString < 0)) return zv::Val(); + if (rightIsString == PT_TRI_YES) { + IETR_VAL(leftValue, callOn(left, mGetValue)); + IETR_VAL(rightValue, callOn(right, mGetValue)); + pt_ietr_binary_op fn = op == OP_AND ? bitwise_and_function : (op == OP_OR ? bitwise_or_function : bitwise_xor_function); + IETR_VAL(value, phpOp(fn, leftValue.raw(), rightValue.raw())); + return newConstantStringType(Z_STR_P(value.raw())); + } + } + } + + IETR_VAL(leftNumberType, callOn(left, mToNumber)); + IETR_VAL(leftNumber, getConstantNumber(leftNumberType.raw())); + IETR_VAL(rightNumberType, callOn(right, mToNumber)); + IETR_VAL(rightNumber, getConstantNumber(rightNumberType.raw())); + if (Z_TYPE_P(leftNumber.raw()) == IS_NULL || Z_TYPE_P(rightNumber.raw()) == IS_NULL) return zv::Val::null(); + + if (op == OP_PLUS || op == OP_MINUS || op == OP_MUL || op == OP_DIV) { + if (op == OP_DIV && isZero(rightNumber.raw())) return zv::Val::null(); + pt_ietr_binary_op fn = op == OP_PLUS ? add_function : (op == OP_MINUS ? sub_function : (op == OP_MUL ? mul_function : div_function)); + IETR_VAL(value, phpOp(fn, leftNumber.raw(), rightNumber.raw())); + return typeFromValue(value.raw()); + } + + // the integer operators read numeric strings out of the int range as the nearest + // integer, like an (int) cast, unlike the float toNumber() gives + IETR_VAL(leftIntegerType, callOn(left, mToInteger)); + IETR_VAL(leftInteger, getConstantInteger(leftIntegerType.raw())); + IETR_VAL(rightIntegerType, callOn(right, mToInteger)); + IETR_VAL(rightInteger, getConstantInteger(rightIntegerType.raw())); + if (Z_TYPE_P(leftInteger.raw()) == IS_NULL || Z_TYPE_P(rightInteger.raw()) == IS_NULL) return zv::Val::null(); + zend_long rightValue = Z_LVAL_P(rightInteger.raw()); + + pt_ietr_binary_op fn; + switch (op) { + case OP_MOD: + if (rightValue == 0) return zv::Val::null(); + fn = mod_function; + break; + case OP_SHIFT_LEFT: + if (rightValue < 0) return zv::Val::null(); + fn = shift_left_function; + break; + case OP_SHIFT_RIGHT: + if (rightValue < 0) return zv::Val::null(); + fn = shift_right_function; + break; + case OP_AND: + fn = bitwise_and_function; + break; + case OP_OR: + fn = bitwise_or_function; + break; + default: + fn = bitwise_xor_function; + break; + } + IETR_VAL(value, phpOp(fn, leftInteger.raw(), rightInteger.raw())); + return newConstantIntegerType(Z_LVAL_P(value.raw())); + } + + /* fold(): IS_NULL when the operands are not few enough constants */ + static zv::Val fold(Op op, ValueSetView left, ValueSetView right) + { + // the bitwise operators list small integer ranges too + bool expandIntegerIntervals = op == OP_AND || op == OP_OR || op == OP_XOR; + std::vector leftTypes, rightTypes; + bool leftFinite, rightFinite; + if (UNEXPECTED(!getFiniteScalarTypes(left, expandIntegerIntervals, leftTypes, leftFinite))) return zv::Val(); + if (UNEXPECTED(!getFiniteScalarTypes(right, expandIntegerIntervals, rightTypes, rightFinite))) return zv::Val(); + if ( + !leftFinite + || !rightFinite + || leftTypes.empty() + || rightTypes.empty() + || (zend_long) leftTypes.size() * (zend_long) rightTypes.size() > PT_IETR_CALCULATE_SCALARS_LIMIT + ) { + return zv::Val::null(); + } + + zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftTypes.size() * rightTypes.size())); + for (zv::Val &leftType : leftTypes) { + for (zv::Val &rightType : rightTypes) { + IETR_VAL(resultType, foldValues(op, leftType.raw(), rightType.raw())); + if (Z_TYPE_P(resultType.raw()) == IS_NULL) return newErrorType(); + resultTypes.push(std::move(resultType)); + } + } + + return unionOf(resultTypes); + } + + /* }}} */ + + /* {{{ the operators */ + + /* getResultOfOperands(): IS_NULL for none */ + static zv::Val getResultOfOperands(ValueSetView left, ValueSetView right) + { + if (isEmpty(left) || isEmpty(right)) { + // keep an explicit never, it ends the execution + for (zval *never : {left.never(), right.never()}) { + if (never == nullptr) continue; + IETR_VAL(isExplicit, callOn(never, mIsExplicit)); + if (Z_TYPE_P(isExplicit.raw()) == IS_TRUE) return zv::Val::copyOf(zv::Ref(never)); + } + return newNeverType(); + } + + if (left.invalid() || right.invalid()) return newErrorType(); + + return zv::Val::null(); + } + + static zv::Val invalidOrFold(Op op, ValueSetView left, ValueSetView right) + { + if (left.invalid() || right.invalid()) return newErrorType(); + return fold(op, left, right); + } + + /* getResultOfOperands() ?? fold() */ + static zv::Val resultOrFold(Op op, ValueSetView left, ValueSetView right) + { + IETR_VAL(result, getResultOfOperands(left, right)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + return fold(op, left, right); + } + + /* getZeroDivisorError(): IS_NULL for none */ + static zv::Val getZeroDivisorError(ValueSetView s) + { + std::vector types; + bool finite; + if (UNEXPECTED(!getFiniteScalarTypes(s, false, types, finite))) return zv::Val(); + if (!finite) return zv::Val::null(); + for (zv::Val &type : types) { + IETR_VAL(numberType, callOn(type.raw(), mToNumber)); + IETR_VAL(number, getConstantNumber(numberType.raw())); + if (isZero(number.raw())) return newErrorType(); + } + return zv::Val::null(); + } + + static zv::Val toResult(ValueSetView left, ValueSetView right, zv::Val result) + { + if (UNEXPECTED(result.isUndef())) return zv::Val(); + if (left.benevolent() || left.unknown() || right.benevolent() || right.unknown()) return toBenevolentUnion(result.raw()); + return result; + } + + /* generalize() */ + static zv::Val generalize(ValueSetView s) + { + bool hasBooleans = s.false_() || s.true_(); + bool hasStrings = s.hasConstantStrings(); + bool hasNumbers = !numbersEmpty(s.numbers()); + if (!hasBooleans && !hasStrings && !s.null_() && hasNumbers) { + NumbersView n(s.numbers()); + if (n.floatCount() == 0 && !n.nan()) return createNumbersOf(anyInteger()); + if (n.integerCount() == 0) { + NumberSetData anyFloat; + anyFloat.floatIntervals.push_back({-HUGE_VAL, HUGE_VAL}); + anyFloat.nan = true; + return createNumbersOf(anyFloat); + } + } + if (!hasBooleans && !hasNumbers && !s.null_() && hasStrings) { + IETR_VAL(stringType, newStringType()); + return createStrings(stringType.raw()); + } + if (!hasBooleans && !hasNumbers && !hasStrings && s.null_()) return createNull(); + + return createInvalid(); + } + + /* generalizeTooManyConstants(); false = pending exception */ + static bool generalizeTooManyConstants(ValueSetView left, ValueSetView right, SetRef &leftOut, SetRef &rightOut) + { + leftOut.object = left.object(); + rightOut.object = right.object(); + std::vector leftTypes, rightTypes; + bool leftFinite, rightFinite; + if (UNEXPECTED(!getFiniteScalarTypes(left, false, leftTypes, leftFinite))) return false; + if (UNEXPECTED(!getFiniteScalarTypes(right, false, rightTypes, rightFinite))) return false; + if ( + !leftFinite + || !rightFinite + || leftTypes.empty() + || rightTypes.empty() + || (zend_long) leftTypes.size() * (zend_long) rightTypes.size() <= PT_IETR_CALCULATE_SCALARS_LIMIT + ) { + return true; + } + + leftOut.owned = generalize(left); + if (UNEXPECTED(leftOut.owned.isUndef())) return false; + rightOut.owned = generalize(right); + if (UNEXPECTED(rightOut.owned.isUndef())) return false; + leftOut.object = Z_OBJ_P(leftOut.owned.raw()); + rightOut.object = Z_OBJ_P(rightOut.owned.raw()); + return true; + } + + /* toNumberType(): IS_NULL for an error */ + static zv::Val toNumberType(ValueSetView s) + { + if (s.unknown()) { + IETR_VAL(integerType, newIntegerType()); + IETR_VAL(floatType, newFloatType()); + return union2(integerType.raw(), floatType.raw()); + } + if (isEmpty(s)) { + if (s.never() != nullptr) return zv::Val::copyOf(zv::Ref(s.never())); + return newNeverType(); + } + + zv::Arr types = zv::Arr::create(4); + if (s.null_() || s.false_()) { + IETR_VAL(zero, newConstantIntegerType(0)); + types.push(std::move(zero)); + } + if (s.true_()) { + IETR_VAL(one, newConstantIntegerType(1)); + types.push(std::move(one)); + } + if (!numbersEmpty(s.numbers())) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return zv::Val(); + IETR_VAL(numbersType, createTypeOfNumbers(numbers)); + types.push(std::move(numbersType)); + } + + std::vector otherTypes; + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + IETR_VAL(constantType, newConstantStringType(Z_STR_P(entry))); + otherTypes.push_back(std::move(constantType)); + } ZEND_HASH_FOREACH_END(); + for (zval *type : {s.strings(), s.arrays(), s.objects()}) { + if (type == nullptr) continue; + otherTypes.push_back(zv::Val::copyOf(zv::Ref(type))); + } + if (s.resources()) { + IETR_VAL(resource, resourceType()); + otherTypes.push_back(std::move(resource)); + } + for (zv::Val &type : otherTypes) { + IETR_VAL(numberType, callOn(type.raw(), mToNumber)); + if (isA(numberType.raw(), pt_ce_error_type)) { + if (s.benevolent()) continue; + return zv::Val::null(); + } + types.push(std::move(numberType)); + } + + if (zend_hash_num_elements(types.table()) == 0) return zv::Val::null(); + + return unionOf(types); + } + + /* calculateCommonMath() of + - * / << >> */ + static zv::Val calculateCommonMath(Op op, ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(leftNumberType, toNumberType(left)); + IETR_VAL(rightNumberType, toNumberType(right)); + bool leftHasType = Z_TYPE_P(leftNumberType.raw()) != IS_NULL; + bool rightHasType = Z_TYPE_P(rightNumberType.raw()) != IS_NULL; + bool isBenevolent = left.benevolent() + || right.benevolent() + || (leftHasType && isA(leftNumberType.raw(), pt_ce_benevolent_union_type)) + || (rightHasType && isA(rightNumberType.raw(), pt_ce_benevolent_union_type)); + + if (!left.unknown() && !right.unknown() && leftHasType && rightHasType) { + NumberSetData leftNumbers, rightNumbers; + bool leftHas, rightHas; + if (UNEXPECTED(!typeNumbers(leftNumberType.raw(), leftNumbers, leftHas))) return zv::Val(); + if (UNEXPECTED(!typeNumbers(rightNumberType.raw(), rightNumbers, rightHas))) return zv::Val(); + if (leftHas && rightHas) { + IetrBinaryKind kind; + switch (op) { + case OP_PLUS: kind = IETR_PLUS; break; + case OP_MINUS: kind = IETR_MINUS; break; + case OP_MUL: kind = IETR_MUL; break; + case OP_DIV: kind = IETR_DIV; break; + case OP_SHIFT_LEFT: kind = IETR_SHIFT_LEFT; break; + default: kind = IETR_SHIFT_RIGHT; break; + } + IETR_VAL(result, pt_number_set_calculate(kind, leftNumbers, rightNumbers, phpVersion)); + return isBenevolent ? toBenevolentUnion(result.raw()) : std::move(result); + } + } + + zend_long leftIsArray = isArray(left); + if (UNEXPECTED(leftIsArray < 0)) return zv::Val(); + if (leftIsArray == PT_TRI_YES) return newErrorType(); + zend_long rightIsArray = isArray(right); + if (UNEXPECTED(rightIsArray < 0)) return zv::Val(); + if (rightIsArray == PT_TRI_YES) return newErrorType(); + if (!leftHasType || !rightHasType) return newErrorType(); + bool leftIsNever = isA(leftNumberType.raw(), pt_ce_never_type); + bool rightIsNever = isA(rightNumberType.raw(), pt_ce_never_type); + if (leftIsNever || rightIsNever) { + // keep an explicit never, it ends the execution + for (zval *numberType : {leftNumberType.raw(), rightNumberType.raw()}) { + if (!isA(numberType, pt_ce_never_type)) continue; + IETR_VAL(isExplicit, callOn(numberType, mIsExplicit)); + if (Z_TYPE_P(isExplicit.raw()) == IS_TRUE) return zv::Val::copyOf(zv::Ref(numberType)); + } + return newNeverType(); + } + + zend_long leftIsFloat = triOn(leftNumberType.raw(), mIsFloat); + if (UNEXPECTED(leftIsFloat < 0)) return zv::Val(); + zend_long rightIsFloat = leftIsFloat == PT_TRI_YES ? PT_TRI_YES : triOn(rightNumberType.raw(), mIsFloat); + if (UNEXPECTED(rightIsFloat < 0)) return zv::Val(); + if (leftIsFloat == PT_TRI_YES || rightIsFloat == PT_TRI_YES) { + if (op == OP_SHIFT_LEFT || op == OP_SHIFT_RIGHT) return newIntegerType(); + return newFloatType(); + } + + IETR_VAL(resultType, union2(leftNumberType.raw(), rightNumberType.raw())); + bool isUnknownOperand = left.unknown() || right.unknown(); + if (op == OP_DIV) { + bool benevolent = isUnknownOperand; + if (!benevolent) { + zend_long isInteger = triOn(resultType.raw(), mIsInteger); + if (UNEXPECTED(isInteger < 0)) return zv::Val(); + benevolent = isInteger == PT_TRI_YES; + } + return newIntFloatUnion(benevolent); + } + + if (isUnknownOperand || left.benevolent() || right.benevolent()) return toBenevolentUnion(resultType.raw()); + + return resultType; + } + + static zv::Val arithmeticAdd(ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(result, resultOrFold(OP_PLUS, left, right)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + + SetRef generalizedLeft, generalizedRight; + if (UNEXPECTED(!generalizeTooManyConstants(left, right, generalizedLeft, generalizedRight))) return zv::Val(); + if (generalizedLeft.object != left.object()) return arithmeticAdd(generalizedLeft.view(), generalizedRight.view(), phpVersion); + + zend_long leftIsArray = isArray(left); + if (UNEXPECTED(leftIsArray < 0)) return zv::Val(); + zend_long rightIsArray = isArray(right); + if (UNEXPECTED(rightIsArray < 0)) return zv::Val(); + if (left.unknown() && right.unknown()) { + IETR_VAL(floatType, newFloatType()); + IETR_VAL(integerType, newIntegerType()); + if (leftIsArray == PT_TRI_NO && rightIsArray == PT_TRI_NO) return newUnionOf(true, {&floatType, &integerType}); + IETR_VAL(arrayType, mixedArrayType()); + return newUnionOf(true, {&floatType, &integerType, &arrayType}); + } + 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(resultType, mixedArrayType()); + if (isOnlyNonEmptyArrays(left) || isOnlyNonEmptyArrays(right)) { + IETR_VAL(nonEmpty, nonEmptyArrayType()); + zv::Args argv{resultType.raw(), nonEmpty.raw()}; + return pt_type_combinator_intersect(2, argv); + } + return resultType; + } + return calculateCommonMath(OP_PLUS, left, right, phpVersion); + } + + /* arithmeticSubtract() / arithmeticMultiply() */ + static zv::Val subtractOrMultiply(Op op, ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(result, invalidOrFold(op, left, right)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + + SetRef generalizedLeft, generalizedRight; + if (UNEXPECTED(!generalizeTooManyConstants(left, right, generalizedLeft, generalizedRight))) return zv::Val(); + return calculateCommonMath(op, generalizedLeft.view(), generalizedRight.view(), phpVersion); + } + + static zv::Val arithmeticDivide(ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(result, invalidOrFold(OP_DIV, left, right)); + if (Z_TYPE_P(result.raw()) == IS_NULL) { + SetRef generalizedLeft, generalizedRight; + if (UNEXPECTED(!generalizeTooManyConstants(left, right, generalizedLeft, generalizedRight))) return zv::Val(); + result = getZeroDivisorError(generalizedRight.view()); + if (UNEXPECTED(result.isUndef())) return zv::Val(); + if (Z_TYPE_P(result.raw()) == IS_NULL) { + result = calculateCommonMath(OP_DIV, generalizedLeft.view(), generalizedRight.view(), phpVersion); + if (UNEXPECTED(result.isUndef())) return zv::Val(); + } + } + if (!isOnlyIntegers(left) || !isOnlyIntegers(right)) return result; + + IETR_VAL(moduloType, arithmeticModulo(left, right, phpVersion)); + zend_long isInteger = triOn(moduloType.raw(), mIsInteger); + if (UNEXPECTED(isInteger < 0)) return zv::Val(); + if (isInteger != PT_TRI_YES) return result; + IETR_VAL(zero, newConstantIntegerType(0)); + zend_long isZeroModulo = triOn(zero.raw(), mIsSuperTypeOf, 1, moduloType.raw()); + if (UNEXPECTED(isZeroModulo < 0)) return zv::Val(); + if (isZeroModulo != PT_TRI_YES) return result; + + // PHP_INT_MIN / -1 divides without a remainder but still overflows to a float + IETR_VAL(floatType, newFloatType()); + IETR_VAL(withoutFloat, pt_type_combinator_remove(result.raw(), floatType.raw())); + return isA(withoutFloat.raw(), pt_ce_never_type) ? std::move(result) : std::move(withoutFloat); + } + + static zv::Val arithmeticIntDivide(ValueSetView left, ValueSetView right) + { + zval *leftNumbers = getIntegerNumbers(left); + zval *rightNumbers = getIntegerNumbers(right); + if (leftNumbers == nullptr || rightNumbers == nullptr) return newErrorType(); + + NumberSetData leftData, rightData; + if (UNEXPECTED(!readNumbers(leftNumbers, leftData) || !readNumbers(rightNumbers, rightData))) return zv::Val(); + return pt_number_set_int_divide(leftData, rightData); + } + + static zv::Val arithmeticModulo(ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(result, resultOrFold(OP_MOD, left, right)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + + SetRef generalizedLeft, generalizedRight; + if (UNEXPECTED(!generalizeTooManyConstants(left, right, generalizedLeft, generalizedRight))) return zv::Val(); + if (generalizedLeft.object != left.object()) return arithmeticModulo(generalizedLeft.view(), generalizedRight.view(), phpVersion); + + NumberSetData leftNumbers, rightNumbers; + bool leftHas, rightHas; + if (UNEXPECTED(!toOperandNumbers(left, false, true, leftNumbers, leftHas))) return zv::Val(); + if (UNEXPECTED(!toOperandNumbers(right, false, true, rightNumbers, rightHas))) return zv::Val(); + if (!leftHas || !rightHas) return newErrorType(); + + IETR_VAL(zeroDivisorError, getZeroDivisorError(right)); + if (Z_TYPE_P(zeroDivisorError.raw()) != IS_NULL) return zeroDivisorError; + + return toResult(left, right, pt_number_set_modulo(leftNumbers, rightNumbers)); + } + + /* getMembers() */ + static bool getMembers(ValueSetView s, std::vector &members) + { + if (s.null_()) { + zv::Val member = createNull(); + if (UNEXPECTED(member.isUndef())) return false; + members.push_back(std::move(member)); + } + if (s.false_() || s.true_()) { + zv::Val member = createBooleans(s.false_(), s.true_()); + if (UNEXPECTED(member.isUndef())) return false; + members.push_back(std::move(member)); + } + if (s.numbers() != nullptr) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return false; + bool nan = numbers.nan; + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + NumberSetData member; + member.integerIntervals.push_back(interval); + zv::Val memberSet = createNumbersOf(member); + if (UNEXPECTED(memberSet.isUndef())) return false; + members.push_back(std::move(memberSet)); + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + bool withNan = nan && interval.min == -HUGE_VAL && interval.max == HUGE_VAL; + nan = nan && !withNan; + NumberSetData member; + member.floatIntervals.push_back(interval); + member.nan = withNan; + zv::Val memberSet = createNumbersOf(member); + if (UNEXPECTED(memberSet.isUndef())) return false; + members.push_back(std::move(memberSet)); + } + if (nan) { + NumberSetData member; + member.nan = true; + zv::Val memberSet = createNumbersOf(member); + if (UNEXPECTED(memberSet.isUndef())) return false; + members.push_back(std::move(memberSet)); + } + } + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + zv::Val member = createConstantString(Z_STR_P(entry)); + if (UNEXPECTED(member.isUndef())) return false; + members.push_back(std::move(member)); + } ZEND_HASH_FOREACH_END(); + if (s.strings() != nullptr) { + zv::Val member = createStrings(s.strings()); + if (UNEXPECTED(member.isUndef())) return false; + members.push_back(std::move(member)); + } + return true; + } + + static zv::Val powerMember(ValueSetView base, ValueSetView exponent, zval *phpVersion) + { + zval *entry; + ZEND_HASH_FOREACH_VAL(exponent.constantStrings(), entry) { + if (!isNumeric(Z_STR_P(entry))) return newErrorType(); + } ZEND_HASH_FOREACH_END(); + + std::vector baseTypes; + bool baseFinite; + if (UNEXPECTED(!getFiniteScalarTypes(base, false, baseTypes, baseFinite))) return zv::Val(); + if (baseFinite && baseTypes.size() == 1) { + std::vector exponentTypes; + bool exponentFinite; + if (UNEXPECTED(!getFiniteScalarTypes(exponent, false, exponentTypes, exponentFinite))) return zv::Val(); + if (exponentFinite && exponentTypes.size() == 1) { + IETR_VAL(baseValue, callOn(baseTypes[0].raw(), mGetValue)); + IETR_VAL(exponentValue, callOn(exponentTypes[0].raw(), mGetValue)); + IETR_VAL(folded, foldPower(baseValue.raw(), exponentValue.raw())); + if (Z_TYPE_P(folded.raw()) == IS_NULL) return newErrorType(); + return folded; + } + + IETR_VAL(baseValue, callOn(baseTypes[0].raw(), mGetValue)); + if (Z_TYPE_P(baseValue.raw()) == IS_STRING && !isNumeric(Z_STR_P(baseValue.raw()))) return newErrorType(); + } + + NumberSetData baseNumbers, exponentNumbers; + bool hasBaseNumbers, hasExponentNumbers; + if (UNEXPECTED(!toNumberSetOfNumber(base, baseNumbers, hasBaseNumbers))) return zv::Val(); + if (UNEXPECTED(!toNumberSetOfNumber(exponent, exponentNumbers, hasExponentNumbers))) return zv::Val(); + if (hasBaseNumbers && hasExponentNumbers) { + IETR_VAL(baseSet, pt_number_set_new(baseNumbers)); + IETR_VAL(exponentSet, pt_number_set_new(exponentNumbers)); + zv::Args argv{exponentSet.raw(), phpVersion}; + IETR_VAL(result, callOn(baseSet.raw(), mPower, 2, argv)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + } + + // a float operand makes the result a float + bool isFloatResult = isOnlyFloats(base) || (hasExponentNumbers && isOnlyFloatNumbers(exponentNumbers)); + if (hasExponentNumbers && isOnlyInteger(exponentNumbers, 0)) { + if (isFloatResult) return newConstantFloatType(1.0); + if (hasBaseNumbers && !baseNumbers.integerIntervals.empty() && baseNumbers.floatIntervals.empty() && !baseNumbers.nan) return newConstantIntegerType(1); + + IETR_VAL(one, newConstantIntegerType(1)); + IETR_VAL(oneFloat, newConstantFloatType(1.0)); + return newUnionOf(false, {&one, &oneFloat}); + } + + if (isFloatResult) return newFloatType(); + + if (hasExponentNumbers && isOnlyInteger(exponentNumbers, 1) && base.numbers() != nullptr && !mayBeNonNumber(base)) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(base.numbers(), numbers))) return zv::Val(); + return createTypeOfNumbers(numbers); + } + + return newIntFloatUnion(true, true); + } + + static zv::Val arithmeticPower(ValueSetView base, ValueSetView exponent, zval *phpVersion) + { + if (isEmpty(exponent)) return newNeverType(); + if ( + exponent.unknown() + || exponent.emptyArray() + || exponent.nonEmptyArray() + || exponent.objects() != nullptr + || exponent.resources() + ) { + return newErrorType(); + } + + std::vector members; + if (UNEXPECTED(!getMembers(exponent, members))) return zv::Val(); + zv::Arr resultTypes = zv::Arr::create((uint32_t) members.size()); + for (zv::Val &member : members) { + IETR_VAL(resultType, powerMember(base, ValueSetView(Z_OBJ_P(member.raw())), phpVersion)); + resultTypes.push(std::move(resultType)); + } + + return unionOf(resultTypes); + } + + static zv::Val arithmeticNegate(ValueSetView s, zval *phpVersion) + { + if (isEmpty(s)) { + if (s.never() != nullptr) return zv::Val::copyOf(zv::Ref(s.never())); + return newNeverType(); + } + + NumberSetData numbers; + bool has = false; + if (!s.invalid() && UNEXPECTED(!toOperandNumbers(s, false, false, numbers, has))) return zv::Val(); + if (!has) return newErrorType(); + + IETR_VAL(negated, pt_number_set_negate(numbers, phpVersion)); + return s.benevolent() ? toBenevolentUnion(negated.raw()) : std::move(negated); + } + + static zv::Val calculateShift(Op op, ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(result, resultOrFold(op, left, right)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + + SetRef generalizedLeft, generalizedRight; + if (UNEXPECTED(!generalizeTooManyConstants(left, right, generalizedLeft, generalizedRight))) return zv::Val(); + IETR_VAL(leftNumberType, toNumberType(generalizedLeft.view())); + if (Z_TYPE_P(leftNumberType.raw()) == IS_NULL) return newErrorType(); + IETR_VAL(rightNumberType, toNumberType(generalizedRight.view())); + if (Z_TYPE_P(rightNumberType.raw()) == IS_NULL) return newErrorType(); + + return calculateCommonMath(op, generalizedLeft.view(), generalizedRight.view(), phpVersion); + } + + static zv::Val calculateBitwise(Op op, ValueSetView left, ValueSetView right) + { + IETR_VAL(result, resultOrFold(op, left, right)); + if (Z_TYPE_P(result.raw()) != IS_NULL) return result; + + bool leftOnlyStrings = isOnlyStrings(left); + bool rightOnlyStrings = isOnlyStrings(right); + if ((leftOnlyStrings || left.unknown()) && (rightOnlyStrings || right.unknown())) { + if (leftOnlyStrings && rightOnlyStrings) return newStringType(); + + // with an unknown operand that is not a string, the string is a number + IETR_VAL(integerType, newIntegerType()); + IETR_VAL(stringType, newStringType()); + return newUnionOf(true, {&integerType, &stringType}); + } + if (mayBeString(left) && !leftOnlyStrings && mayBeString(right) && !rightOnlyStrings) return newErrorType(); + + NumberSetData leftNumbers, rightNumbers; + bool leftHas, rightHas; + if (UNEXPECTED(!toOperandNumbers(left, false, false, leftNumbers, leftHas))) return zv::Val(); + if (UNEXPECTED(!toOperandNumbers(right, false, false, rightNumbers, rightHas))) return zv::Val(); + if (!leftHas || !rightHas) return newErrorType(); + + IetrBinaryKind kind = op == OP_AND ? IETR_BITWISE_AND : (op == OP_OR ? IETR_BITWISE_OR : IETR_BITWISE_XOR); + return toResult(left, right, pt_number_set_bitwise(kind, leftNumbers, rightNumbers)); + } + + /* }}} */ + + /* {{{ loose equality */ + + static zv::Val looseComparisonHelper(const char *lcMethod, size_t len, uint32_t argc, zval *argv) + { + return pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, lcMethod, len, argc, argv); + } + + /* getNumberString() */ + static zv::Val getNumberString(zval *number) + { + if (Z_TYPE_P(number) == IS_LONG) return zv::Val::adoptString(zend_long_to_str(Z_LVAL_P(number))); + double value = Z_DVAL_P(number); + if (std::isinf(value)) return value > 0 ? zv::Val::string(PT_LC("INF")) : zv::Val::string(PT_LC("-INF")); + + static zend_function *sprintfFunction = NULL; + for (zend_long digits : {SHORT_FLOAT_DIGITS, EXACT_FLOAT_DIGITS}) { + zend_string *format = zend_strpprintf(0, "%%.%dG", (int) digits); + zval argv[2]; + ZVAL_STR(&argv[0], format); + ZVAL_DOUBLE(&argv[1], value); + zv::Val string = callInternalFunction(sprintfFunction, PT_LC("sprintf"), 2, argv); + zend_string_release(format); + if (UNEXPECTED(string.isUndef())) return zv::Val(); + if (digits == EXACT_FLOAT_DIGITS || zend_strtod(Z_STRVAL_P(string.raw()), NULL) == value) return string; + } + return zv::Val(); + } + + /* getLooselyEqualNumber(): IS_NULL for none */ + static zv::Val getLooselyEqualNumber(zval *value, zval *phpVersion) + { + if (Z_TYPE_P(value) == IS_NULL) return zv::Val::integer(0); + if (Z_TYPE_P(value) == IS_TRUE || Z_TYPE_P(value) == IS_FALSE || (Z_TYPE_P(value) == IS_DOUBLE && std::isnan(Z_DVAL_P(value)))) return zv::Val::null(); + if (Z_TYPE_P(value) != IS_STRING) return zv::Val::copyOf(zv::Ref(value)); + + zv::Args parseArgs{value, phpVersion}; + IETR_VAL(number, looseComparisonHelper(PT_LC("parsenumericstring"), 2, parseArgs)); + if (Z_TYPE_P(number.raw()) == IS_ARRAY) { + zval *parsed = zend_hash_index_find(Z_ARRVAL_P(number.raw()), 0); + return zv::Val::copyOf(zv::Ref(parsed)); + } + bool casts; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, casts))) return zv::Val(); + if (!casts) { + // the number the string starts with + IETR_VAL(prefix, looseComparisonHelper(PT_LC("parsenumericprefix"), 1, value)); + if (Z_TYPE_P(prefix.raw()) == IS_NULL) return zv::Val::integer(0); + return prefix; + } + + // a number compares with a non-numeric string as its own string + for (double infinity : {HUGE_VAL, -HUGE_VAL}) { + zval infinityValue; + ZVAL_DOUBLE(&infinityValue, infinity); + IETR_VAL(string, getNumberString(&infinityValue)); + if (zend_string_equals(Z_STR_P(string.raw()), Z_STR_P(value))) return zv::Val::adopt(infinityValue); + } + + return zv::Val::null(); + } + + /* getTypeLooselyEqualTo() */ + static zv::Val getTypeLooselyEqualTo(zval *value, zval *phpVersion) + { + // null compares with a string as with '', with anything else by truthiness, like the booleans + zv::Arr types = zv::Arr::create(8); + bool truthy = zend_is_true(value); + IETR_VAL(booleanType, newConstantBooleanType(truthy)); + types.push(std::move(booleanType)); + if (Z_TYPE_P(value) == IS_STRING ? Z_STRLEN_P(value) == 0 : !truthy) { + IETR_VAL(nullType, newNullType()); + types.push(std::move(nullType)); + } + if (Z_TYPE_P(value) == IS_NULL) { + // and with an array by its emptiness + IETR_VAL(emptyArray, emptyConstantArrayType()); + types.push(std::move(emptyArray)); + } + + IETR_VAL(number, getLooselyEqualNumber(value, phpVersion)); + bool hasNumber = Z_TYPE_P(number.raw()) != IS_NULL; + if (hasNumber) { + // both zeros + double floatNumber = zval_get_double(number.raw()); + IETR_VAL(floatType, pt_float_range_from_interval(floatNumber, floatNumber)); + types.push(std::move(floatType)); + if (Z_TYPE_P(number.raw()) == IS_LONG) { + IETR_VAL(integerType, newConstantIntegerType(Z_LVAL_P(number.raw()))); + types.push(std::move(integerType)); + } else if (std::floor(floatNumber) == floatNumber && std::fabs(floatNumber) <= (double) EXACT_FLOAT_INTEGER_LIMIT) { + IETR_VAL(integerType, newConstantIntegerType((zend_long) floatNumber)); + types.push(std::move(integerType)); + } + } + + // the string itself, the number's own string, and the strings that are falsy + std::vector strings; + strings.push_back(zv::Val::string(PT_LC(""))); + strings.push_back(zv::Val::string(PT_LC("0"))); + if (hasNumber) { + IETR_VAL(numberString, getNumberString(number.raw())); + strings.push_back(std::move(numberString)); + } + if (Z_TYPE_P(value) == IS_STRING) strings.push_back(zv::Val::copyOf(zv::Ref(value))); + for (zv::Val &string : strings) { + zv::Args compareArgs{value, string.raw(), phpVersion}; + IETR_VAL(comparison, looseComparisonHelper(PT_LC("comparevalues"), 3, compareArgs)); + if (Z_TYPE_P(comparison.raw()) != IS_LONG || Z_LVAL_P(comparison.raw()) != 0) continue; + + IETR_VAL(stringType, newConstantStringType(Z_STR_P(string.raw()))); + types.push(std::move(stringType)); + } + + return unionOf(types); + } + + /* getIntegerTypeInInterval() */ + static zv::Val getIntegerTypeInInterval(double min, double max) + { + if (min > max || min > (double) ZEND_LONG_MAX || max < (double) ZEND_LONG_MIN) return newNeverType(); + + constexpr double limit = (double) EXACT_FLOAT_INTEGER_LIMIT; + NullableLong integerMin, integerMax; + if (min < -limit) { + integerMin = NullableLong::null(); + } else if (min > limit) { + integerMin = NullableLong::of(EXACT_FLOAT_INTEGER_LIMIT); + } else { + integerMin = NullableLong::of((zend_long) std::ceil(min)); + } + + if (max > limit) { + integerMax = NullableLong::null(); + } else if (max < -limit) { + integerMax = NullableLong::of(-EXACT_FLOAT_INTEGER_LIMIT); + } else { + integerMax = NullableLong::of((zend_long) std::floor(max)); + } + + return fromInterval(integerMin, integerMax); + } + + /* createNumberType() */ + static zv::Val createNumberType(zval *number) + { + double floatNumber = zval_get_double(number); + IETR_VAL(integerType, Z_TYPE_P(number) == IS_LONG ? newConstantIntegerType(Z_LVAL_P(number)) : getIntegerTypeInInterval(floatNumber, floatNumber)); + IETR_VAL(floatType, pt_float_range_from_interval(floatNumber, floatNumber)); + return union2(integerType.raw(), floatType.raw()); + } + + /* $number >= 1 && floor($number) === (float) $number */ + static bool isResourceId(zval *number) + { + if (Z_TYPE_P(number) == IS_LONG) return Z_LVAL_P(number) >= 1; + double value = Z_DVAL_P(number); + return value >= 1.0 && std::floor(value) == value; + } + + /* getBooleanAndNullPartnerType() */ + static zv::Val getBooleanAndNullPartnerType(bool canBeZero, bool canBeNonZero) + { + zv::Arr types = zv::Arr::create(3); + if (canBeZero) { + IETR_VAL(nullType, newNullType()); + types.push(std::move(nullType)); + IETR_VAL(falseType, newConstantBooleanType(false)); + types.push(std::move(falseType)); + } + if (canBeNonZero) { + IETR_VAL(trueType, newConstantBooleanType(true)); + types.push(std::move(trueType)); + } + return unionOf(types); + } + + static bool canBeZero(const NumberSetData &numbers) + { + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + zend_long min = interval.min.isNull ? ZEND_LONG_MIN : interval.min.value; + zend_long max = interval.max.isNull ? ZEND_LONG_MAX : interval.max.value; + if (min <= 0 && max >= 0) return true; + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + if (interval.min <= 0.0 && interval.max >= 0.0) return true; + } + return false; + } + + static bool canBeNonZero(const NumberSetData &numbers) + { + if (numbers.nan) return true; + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + if (interval.min.isNull || interval.max.isNull || interval.min.value != interval.max.value || interval.min.value != 0) return true; + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + if (interval.min != interval.max || interval.min != 0.0) return true; + } + return false; + } + + /* getNumberPartnerType() */ + static zv::Val getNumberPartnerType(const NumberSetData &numbers, zval *phpVersion) + { + // an integer and a float are equal when the integer converted to float is the float + zv::Arr integerTypes = zv::Arr::create(4); + std::vector floatIntervals; + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + IETR_VAL(rangeType, fromInterval(interval.min, interval.max)); + integerTypes.push(std::move(rangeType)); + floatIntervals.push_back({ + (double) (interval.min.isNull ? ZEND_LONG_MIN : interval.min.value), + (double) (interval.max.isNull ? ZEND_LONG_MAX : interval.max.value), + }); + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + IETR_VAL(integerType, getIntegerTypeInInterval(interval.min, interval.max)); + integerTypes.push(std::move(integerType)); + floatIntervals.push_back(interval); + } + if (floatIntervals.empty()) return newNeverType(); + + IETR_VAL(integerType, unionOf(integerTypes)); + zv::Arr types = zv::Arr::create(8); + types.push(zv::Ref(integerType.raw())); + IETR_VAL(numericString, createNumericStringType()); + types.push(std::move(numericString)); + bool containsOne = false; + bool containsInfinity = false; + bool containsNegativeInfinity = false; + for (const NumberSetData::FloatInterval &interval : floatIntervals) { + IETR_VAL(rangeType, pt_float_range_from_interval(interval.min, interval.max)); + types.push(std::move(rangeType)); + containsOne = containsOne || (interval.min <= 1.0 && interval.max >= 1.0); + containsInfinity = containsInfinity || interval.max == HUGE_VAL; + containsNegativeInfinity = containsNegativeInfinity || interval.min == -HUGE_VAL; + } + + // an object compares as 1, unless it is number-like + IETR_VAL(objectType, containsOne ? objectWithoutClassType() : createNumberLikeObjectType(phpVersion)); + types.push(std::move(objectType)); + IETR_VAL(positive, fromInterval(NullableLong::of(1), NullableLong::null())); + zv::Args intersectArgs{integerType.raw(), positive.raw()}; + IETR_VAL(positiveIntegers, pt_type_combinator_intersect(2, intersectArgs)); + zend_long isInteger = triOn(positiveIntegers.raw(), mIsInteger); + if (UNEXPECTED(isInteger < 0)) return zv::Val(); + if (isInteger != PT_TRI_NO) { + // as its integer ID + IETR_VAL(resource, resourceType()); + types.push(std::move(resource)); + } + bool casts; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, casts))) return zv::Val(); + if (!casts) { + // as the number the string starts with + IETR_VAL(stringType, newStringType()); + types.push(std::move(stringType)); + } else { + // as its own string + if (containsInfinity) { + IETR_VAL(infinity, constantStringLiteral(PT_LC("INF"))); + types.push(std::move(infinity)); + } + if (containsNegativeInfinity) { + IETR_VAL(negativeInfinity, constantStringLiteral(PT_LC("-INF"))); + types.push(std::move(negativeInfinity)); + } + } + + return unionOf(types); + } + + /* getConstantStringPartnerType() */ + static zv::Val getConstantStringPartnerType(zval *value, zval *phpVersion) + { + zend_string *string = Z_STR_P(value); + zv::Arr types = zv::Arr::create(8); + IETR_VAL(constantType, newConstantStringType(string)); + types.push(std::move(constantType)); + IETR_VAL(stringable, createStringableObjectType(phpVersion)); + types.push(std::move(stringable)); + bool truthy = ZSTR_LEN(string) != 0 && !zend_string_equals_literal(string, "0"); + IETR_VAL(booleanAndNull, getBooleanAndNullPartnerType(false, truthy)); + types.push(std::move(booleanAndNull)); + IETR_VAL(looselyEqual, getTypeLooselyEqualTo(value, phpVersion)); + types.push(std::move(looselyEqual)); + + zv::Args parseArgs{value, phpVersion}; + IETR_VAL(parsed, looseComparisonHelper(PT_LC("parsenumericstring"), 2, parseArgs)); + zv::Val number; + if (Z_TYPE_P(parsed.raw()) == IS_ARRAY) { + number = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(parsed.raw()), 0))); + IETR_VAL(numericString, createNumericStringType()); + types.push(std::move(numericString)); + } else { + bool casts; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, casts))) return zv::Val(); + if (!casts) { + number = looseComparisonHelper(PT_LC("parsenumericprefix"), 1, value); + if (UNEXPECTED(number.isUndef())) return zv::Val(); + if (Z_TYPE_P(number.raw()) == IS_NULL) number = zv::Val::integer(0); + } + } + if (!number.isNull()) { + IETR_VAL(numberType, createNumberType(number.raw())); + types.push(std::move(numberType)); + if (isResourceId(number.raw())) { + IETR_VAL(resource, resourceType()); + types.push(std::move(resource)); + } + } + + return unionOf(types); + } + + /* getStringPartnerType() */ + static zv::Val getStringPartnerType(zval *strings, zval *phpVersion) + { + // a string compares with null as with '', and with a bool by its truthiness + zv::Arr booleanAndNullTypes = zv::Arr::create(3); + IETR_VAL(trueType, newConstantBooleanType(true)); + booleanAndNullTypes.push(std::move(trueType)); + zend_long isNonEmpty = triOn(strings, mIsNonEmptyString); + if (UNEXPECTED(isNonEmpty < 0)) return zv::Val(); + if (isNonEmpty != PT_TRI_YES) { + IETR_VAL(nullType, newNullType()); + booleanAndNullTypes.push(std::move(nullType)); + } + zend_long isNonFalsy = triOn(strings, mIsNonFalsyString); + if (UNEXPECTED(isNonFalsy < 0)) return zv::Val(); + if (isNonFalsy != PT_TRI_YES) { + IETR_VAL(falseType, newConstantBooleanType(false)); + booleanAndNullTypes.push(std::move(falseType)); + } + IETR_VAL(booleanAndNull, unionOf(booleanAndNullTypes)); + + zend_long isNumeric = triOn(strings, mIsNumericString); + if (UNEXPECTED(isNumeric < 0)) return zv::Val(); + zv::Arr types = zv::Arr::create(7); + if (isNumeric == PT_TRI_YES) { + // a numeric string compares with a non-numeric one byte by byte, so they differ + IETR_VAL(integerType, newIntegerType()); + types.push(std::move(integerType)); + IETR_VAL(floatRange, pt_float_range_from_interval(-HUGE_VAL, HUGE_VAL)); + types.push(std::move(floatRange)); + IETR_VAL(numericString, createNumericStringType()); + types.push(std::move(numericString)); + } else { + // a non-numeric string equals only itself + types.push(zv::Ref(strings)); + IETR_VAL(numericString, isNumeric == PT_TRI_NO ? newNeverType() : createNumericStringType()); + types.push(std::move(numericString)); + IETR_VAL(integerType, newIntegerType()); + types.push(std::move(integerType)); + IETR_VAL(floatType, newFloatType()); + types.push(std::move(floatType)); + } + IETR_VAL(stringable, createStringableObjectType(phpVersion)); + types.push(std::move(stringable)); + IETR_VAL(resource, resourceType()); + types.push(std::move(resource)); + types.push(std::move(booleanAndNull)); + + return unionOf(types); + } + + /* getArrayPartnerType() */ + static zv::Val getArrayPartnerType(bool empty, bool nonEmpty) + { + if (!nonEmpty) return emptyArrayPartner(); + + IETR_VAL(arrayType, mixedArrayType()); + if (!empty) { + IETR_VAL(trueType, newConstantBooleanType(true)); + IETR_VAL(nonEmptyType, nonEmptyArrayType()); + zv::Args argv{arrayType.raw(), nonEmptyType.raw()}; + IETR_VAL(nonEmptyArray, pt_type_combinator_intersect(2, argv)); + return union2(trueType.raw(), nonEmptyArray.raw()); + } + + IETR_VAL(booleanType, newBooleanType()); + IETR_VAL(nullType, newNullType()); + zv::Arr types = zv::Arr::create(3); + types.push(std::move(arrayType)); + types.push(std::move(booleanType)); + types.push(std::move(nullType)); + return unionOf(types); + } + + /* union(new NullType(), new ConstantBooleanType(false), new ConstantArrayType([], [])) */ + static zv::Val emptyArrayPartner() + { + zv::Arr types = zv::Arr::create(3); + IETR_VAL(nullType, newNullType()); + types.push(std::move(nullType)); + IETR_VAL(falseType, newConstantBooleanType(false)); + types.push(std::move(falseType)); + IETR_VAL(emptyArray, emptyConstantArrayType()); + types.push(std::move(emptyArray)); + return unionOf(types); + } + + static zv::Val getPossiblyLooselyEqualType(ValueSetView s, zval *phpVersion) + { + if (s.unknown() || s.resources()) return newMixedType(); + + zv::Arr types = zv::Arr::create(8); + if (s.null_()) { + zval nullValue; + ZVAL_NULL(&nullValue); + IETR_VAL(type, getTypeLooselyEqualTo(&nullValue, phpVersion)); + types.push(std::move(type)); + } + if (s.true_()) { + IETR_VAL(truthy, pt_static_type_factory_truthy()); + types.push(std::move(truthy)); + } + if (s.false_()) { + IETR_VAL(falsey, pt_static_type_factory_falsey()); + types.push(std::move(falsey)); + IETR_VAL(numberLike, createNumberLikeObjectType(phpVersion)); + types.push(std::move(numberLike)); + } + if (s.numbers() != nullptr) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return zv::Val(); + IETR_VAL(numberPartner, getNumberPartnerType(numbers, phpVersion)); + types.push(std::move(numberPartner)); + IETR_VAL(booleanAndNull, getBooleanAndNullPartnerType(canBeZero(numbers), canBeNonZero(numbers))); + types.push(std::move(booleanAndNull)); + } + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + IETR_VAL(partner, getConstantStringPartnerType(entry, phpVersion)); + types.push(std::move(partner)); + } ZEND_HASH_FOREACH_END(); + if (s.strings() != nullptr) { + IETR_VAL(partner, getStringPartnerType(s.strings(), phpVersion)); + types.push(std::move(partner)); + } + if (s.emptyArray() || s.nonEmptyArray()) { + IETR_VAL(partner, getArrayPartnerType(s.emptyArray(), s.nonEmptyArray())); + types.push(std::move(partner)); + } + if (s.objects() != nullptr) { + zend_long isEnum = triOn(s.objects(), mIsEnum); + if (UNEXPECTED(isEnum < 0)) return zv::Val(); + if (isEnum != PT_TRI_YES) return newMixedType(); + + // an enum case equals only itself, and true + types.push(zv::Ref(s.objects())); + IETR_VAL(trueType, newConstantBooleanType(true)); + types.push(std::move(trueType)); + } + + return unionOf(types); + } + + static zv::Val getAlwaysLooselyEqualType(ValueSetView s, zval *phpVersion) + { + if (s.unknown() || s.strings() != nullptr || s.nonEmptyArray() || s.resources()) return newNeverType(); + + std::vector types; + if (s.null_()) { + zval nullValue; + ZVAL_NULL(&nullValue); + IETR_VAL(type, getTypeLooselyEqualTo(&nullValue, phpVersion)); + types.push_back(std::move(type)); + } + if (s.true_()) { + IETR_VAL(falsey, pt_static_type_factory_falsey()); + IETR_VAL(numberLike, createNumberLikeObjectType(phpVersion)); + IETR_VAL(subtracted, union2(falsey.raw(), numberLike.raw())); + zval mixed; + if (UNEXPECTED(!pt_mixed_type_new(&mixed, false, subtracted.raw()))) return zv::Val(); + types.push_back(zv::Val::adopt(mixed)); + } + if (s.false_()) { + IETR_VAL(falsey, pt_static_type_factory_falsey()); + types.push_back(std::move(falsey)); + } + if (s.numbers() != nullptr) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return zv::Val(); + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + if (interval.min.isNull || interval.max.isNull || interval.min.value != interval.max.value) return newNeverType(); + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + if (interval.min != interval.max) return newNeverType(); + } + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + zval number; + ZVAL_LONG(&number, interval.min.value); + IETR_VAL(type, getTypeLooselyEqualTo(&number, phpVersion)); + types.push_back(std::move(type)); + } + for (const NumberSetData::FloatInterval &interval : numbers.floatIntervals) { + zval number; + ZVAL_DOUBLE(&number, interval.min); + IETR_VAL(type, getTypeLooselyEqualTo(&number, phpVersion)); + types.push_back(std::move(type)); + } + if (numbers.nan) { + zval number; + ZVAL_DOUBLE(&number, std::numeric_limits::quiet_NaN()); + IETR_VAL(type, getTypeLooselyEqualTo(&number, phpVersion)); + types.push_back(std::move(type)); + } + } + zval *entry; + ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { + IETR_VAL(type, getTypeLooselyEqualTo(entry, phpVersion)); + types.push_back(std::move(type)); + } ZEND_HASH_FOREACH_END(); + if (s.emptyArray()) { + IETR_VAL(partner, emptyArrayPartner()); + types.push_back(std::move(partner)); + } + if (s.objects() != nullptr) { + zend_long isEnum = triOn(s.objects(), mIsEnum); + if (UNEXPECTED(isEnum < 0)) return zv::Val(); + if (isEnum != PT_TRI_YES) return newNeverType(); + IETR_VAL(enumCases, callOn(s.objects(), mGetEnumCases)); + HashTable *cases = Z_ARRVAL_P(enumCases.raw()); + if (zend_hash_num_elements(cases) != 1) return newNeverType(); + + IETR_VAL(trueType, newConstantBooleanType(true)); + IETR_VAL(type, union2(zend_hash_index_find(cases, 0), trueType.raw())); + types.push_back(std::move(type)); + } + + // one value at a time: intersecting all the unions at once multiplies them out + zv::Val alwaysEqualType; + for (zv::Val &type : types) { + if (alwaysEqualType.isUndef()) { + alwaysEqualType = std::move(type); + } else { + zv::Args argv{alwaysEqualType.raw(), type.raw()}; + alwaysEqualType = pt_type_combinator_intersect(2, argv); + if (UNEXPECTED(alwaysEqualType.isUndef())) return zv::Val(); + } + if (isA(alwaysEqualType.raw(), pt_ce_never_type)) break; + } + + if (alwaysEqualType.isUndef()) return newNeverType(); + return alwaysEqualType; + } + + /* }}} */ + +private: + static zv::Val objectValue(ValueSetView s) + { + zval object; + ZVAL_OBJ_COPY(&object, s.object()); + return zv::Val::adopt(object); + } +}; + +} // namespace phpstanturbo + +using phpstanturbo::ValueSetOps; +using phpstanturbo::ValueSetView; + +zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc, zval *argv) +{ + return pt_type_call_static_ce(pt_ce_value_set, lcFactory, len, argc, argv); +} + +/* {{{ engine ABI glue: parameter parsing + registration */ + +#define PT_SELF ValueSetView(Z_OBJ_P(ZEND_THIS)) + +/* an argument of the twin's `self` / class-typed parameter: the TypeError + * of its strict-types call for any other value */ +static bool requireInstance(zval *value, zend_class_entry *ce, uint32_t position, const char *name) +{ + if (EXPECTED(Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce))) return true; + zend_type_error("%s::%s(): Argument #%u ($%s) must be of type %s, %s given", ZSTR_VAL(pt_ce_value_set->name), get_active_function_name(), position, name, ZSTR_VAL(ce->name), zend_zval_value_name(value)); + return false; +} + +/* (self $right, PhpVersion $phpVersion) operators */ +template +static void pt_vs_operator(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(right, pt_ce_value_set, 1, "right"))) RETURN_THROWS(); + PT_RETURN_VAL(Operator(PT_SELF, ValueSetView(Z_OBJ_P(right)), phpVersion)); +} + +static zv::Val vsSubtract(ValueSetView left, ValueSetView right, zval *phpVersion) +{ + return ValueSetOps::subtractOrMultiply(ValueSetOps::OP_MINUS, left, right, phpVersion); +} + +static zv::Val vsMultiply(ValueSetView left, ValueSetView right, zval *phpVersion) +{ + return ValueSetOps::subtractOrMultiply(ValueSetOps::OP_MUL, left, right, phpVersion); +} + +static zv::Val vsIntDivide(ValueSetView left, ValueSetView right, zval *) +{ + return ValueSetOps::arithmeticIntDivide(left, right); +} + +template +static zv::Val vsBitwise(ValueSetView left, ValueSetView right, zval *) +{ + return ValueSetOps::calculateBitwise(Op, left, right); +} + +template +static zv::Val vsShift(ValueSetView left, ValueSetView right, zval *phpVersion) +{ + return ValueSetOps::calculateShift(Op, left, right, phpVersion); +} + +PT_MINIT_REGISTRATION(pt_register_value_set) +{ + reg::Class cls("PHPStan\\Type\\ValueSet"); + ptdecl::ValueSet::declareClass(cls); + cls.privateClassConstantLong("EXACT_FLOAT_INTEGER_LIMIT", ValueSetOps::EXACT_FLOAT_INTEGER_LIMIT); + cls.privateClassConstantLong("EXACT_FLOAT_DIGITS", ValueSetOps::EXACT_FLOAT_DIGITS); + cls.privateClassConstantLong("SHORT_FLOAT_DIGITS", ValueSetOps::SHORT_FLOAT_DIGITS); + /* the sixteen properties must stay the declared ones in this order (slots::*) */ + ptdecl::ValueSet::declareProperties(cls); + + cls.method(sigs::createUnknown, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *type = NULL; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OR_NULL(type) + ZEND_PARSE_PARAMETERS_END(); + if (type != NULL && UNEXPECTED(!requireInstance(type, pt_ce_mixed_type, 1, "type"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createUnknown(type)); + }); + cls.method(sigs::isPlusable, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_BOOL(ValueSetOps::isPlusable(PT_SELF)); + }); + cls.method(sigs::isUnknown, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_BOOL(ValueSetOps::isUnknown(PT_SELF)); + }); + cls.method(sigs::createEmpty, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(ValueSetOps::createEmpty()); + }); + cls.method(sigs::createInvalid, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(ValueSetOps::createInvalid()); + }); + cls.method(sigs::createNever, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *never; + if (!zp::parse(execute_data, never)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(never, pt_ce_never_type, 1, "never"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createNever(never)); + }); + cls.method(sigs::createNull, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(ValueSetOps::createNull()); + }); + cls.method(sigs::createBooleans, [](INTERNAL_FUNCTION_PARAMETERS) { + bool falseValue, trueValue; + if (!zp::parse(execute_data, falseValue, trueValue)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createBooleans(falseValue, trueValue)); + }); + cls.method(sigs::createNumbers, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *numbers; + if (!zp::parse(execute_data, numbers)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(numbers, pt_number_set_class(), 1, "numbers"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createNumbers(zv::Val::copyOf(zv::Ref(numbers)))); + }); + cls.method(sigs::createConstantString, [](INTERNAL_FUNCTION_PARAMETERS) { + zend_string *value; + if (!zp::parse(execute_data, value)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createConstantString(value)); + }); + cls.method(sigs::createStrings, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *strings; + if (!zp::parse(execute_data, strings)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createStrings(strings)); + }); + cls.method(sigs::createArrays, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *arrays; + if (!zp::parse(execute_data, arrays)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createArrays(arrays)); + }); + cls.method(sigs::createObjects, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *objects; + if (!zp::parse(execute_data, objects)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createObjects(objects)); + }); + cls.method(sigs::createResources, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(ValueSetOps::createResources()); + }); + cls.method(sigs::toBenevolent, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(ValueSetOps::toBenevolent(Z_OBJ_P(ZEND_THIS))); + }); + cls.method(sigs::isEmpty, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_BOOL(ValueSetOps::isEmpty(PT_SELF)); + }); + cls.method(sigs::union_, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *other; + if (!zp::parse(execute_data, other)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(other, pt_ce_value_set, 1, "other"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::union_(PT_SELF, ValueSetView(Z_OBJ_P(other)))); + }); + cls.method(sigs::intersect, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *other; + if (!zp::parse(execute_data, other)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(other, pt_ce_value_set, 1, "other"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::intersect(PT_SELF, ValueSetView(Z_OBJ_P(other)))); + }); + cls.method(sigs::getPossiblyLooselyEqualType, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *phpVersion; + if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::getPossiblyLooselyEqualType(PT_SELF, phpVersion)); + }); + cls.method(sigs::getAlwaysLooselyEqualType, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *phpVersion; + if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::getAlwaysLooselyEqualType(PT_SELF, phpVersion)); + }); + cls.method(sigs::arithmeticAdd, pt_vs_operator); + cls.method(sigs::arithmeticSubtract, pt_vs_operator); + cls.method(sigs::arithmeticMultiply, pt_vs_operator); + cls.method(sigs::arithmeticDivide, pt_vs_operator); + cls.method(sigs::arithmeticIntDivide, pt_vs_operator); + cls.method(sigs::arithmeticModulo, pt_vs_operator); + cls.method(sigs::arithmeticPower, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *exponent, *phpVersion; + if (!zp::parse(execute_data, exponent, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(exponent, pt_ce_value_set, 1, "exponent"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::arithmeticPower(PT_SELF, ValueSetView(Z_OBJ_P(exponent)), phpVersion)); + }); + cls.method(sigs::arithmeticNegate, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *phpVersion; + if (!zp::parse(execute_data, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::arithmeticNegate(PT_SELF, phpVersion)); + }); + cls.method(sigs::bitwiseAnd, pt_vs_operator>); + cls.method(sigs::bitwiseOr, pt_vs_operator>); + cls.method(sigs::bitwiseXor, pt_vs_operator>); + cls.method(sigs::bitwiseShiftLeft, pt_vs_operator>); + cls.method(sigs::bitwiseShiftRight, pt_vs_operator>); + + cls.shadow(&pt_ce_value_set); +} + +/* }}} */ diff --git a/turbo-ext/src/generated/ValueSet.h b/turbo-ext/src/generated/ValueSet.h new file mode 100644 index 00000000000..3c9c0286ccf --- /dev/null +++ b/turbo-ext/src/generated/ValueSet.h @@ -0,0 +1,493 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Type/ValueSet.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_VALUE_SET_H +#define PHPSTANTURBO_GENERATED_VALUE_SET_H + +#include "../reg.h" + +namespace ptdecl::ValueSet { + +/* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ +namespace slot { +inline constexpr uint32_t unknown = 0; +inline constexpr uint32_t null = 1; +inline constexpr uint32_t false_ = 2; +inline constexpr uint32_t true_ = 3; +inline constexpr uint32_t numbers = 4; +inline constexpr uint32_t constantStrings = 5; +inline constexpr uint32_t strings = 6; +inline constexpr uint32_t emptyArray = 7; +inline constexpr uint32_t nonEmptyArray = 8; +inline constexpr uint32_t arrays = 9; +inline constexpr uint32_t objects = 10; +inline constexpr uint32_t resources = 11; +inline constexpr uint32_t invalid = 12; +inline constexpr uint32_t benevolent = 13; +inline constexpr uint32_t never = 14; +inline constexpr uint32_t unknownType = 15; +} // namespace slot + +inline void declareClass(reg::Class &cls) +{ + cls.final(); +} + +/* the properties the class declares itself, in declaration order (a used trait's come from its registrar) */ +inline void declareProperties(reg::Class &cls) +{ + cls.property("unknown", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("null", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("false", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("true", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("numbers", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\NumberSet"); + cls.property("constantStrings", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); + cls.property("strings", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); + cls.property("emptyArray", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("nonEmptyArray", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("arrays", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); + cls.property("objects", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); + cls.property("resources", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("invalid", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("benevolent", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("never", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\NeverType"); + cls.property("unknownType", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "unknown\0" /* 0 */ + "null\0" /* 8 */ + "false\0" /* 13 */ + "true\0" /* 19 */ + "numbers\0" /* 24 */ + "PHPStan\\Type\\NumberSet\0" /* 32 */ + "constantStrings\0" /* 55 */ + "[]\0" /* 71 */ + "strings\0" /* 74 */ + "PHPStan\\Type\\Type\0" /* 82 */ + "emptyArray\0" /* 100 */ + "nonEmptyArray\0" /* 111 */ + "arrays\0" /* 125 */ + "objects\0" /* 132 */ + "resources\0" /* 140 */ + "invalid\0" /* 150 */ + "benevolent\0" /* 158 */ + "never\0" /* 169 */ + "PHPStan\\Type\\NeverType\0" /* 175 */ + "unknownType\0" /* 198 */ + "__construct\0" /* 210 */ + "type\0" /* 222 */ + "PHPStan\\Type\\MixedType\0" /* 227 */ + "\0" /* 250 */ + "PHPStan\\Type\\ValueSet\0" /* 251 */ + "createUnknown\0" /* 273 */ + "isPlusable\0" /* 287 */ + "isUnknown\0" /* 298 */ + "createEmpty\0" /* 308 */ + "createInvalid\0" /* 320 */ + "createNever\0" /* 334 */ + "createNull\0" /* 346 */ + "createBooleans\0" /* 357 */ + "createNumbers\0" /* 372 */ + "value\0" /* 386 */ + "createConstantString\0" /* 392 */ + "createStrings\0" /* 413 */ + "createArrays\0" /* 427 */ + "createObjects\0" /* 440 */ + "createResources\0" /* 454 */ + "toBenevolent\0" /* 470 */ + "isEmpty\0" /* 483 */ + "other\0" /* 491 */ + "union\0" /* 497 */ + "intersect\0" /* 503 */ + "phpVersion\0" /* 513 */ + "PHPStan\\Php\\PhpVersion\0" /* 524 */ + "getPossiblyLooselyEqualType\0" /* 547 */ + "getAlwaysLooselyEqualType\0" /* 575 */ + "right\0" /* 601 */ + "arithmeticAdd\0" /* 607 */ + "arithmeticSubtract\0" /* 621 */ + "arithmeticMultiply\0" /* 640 */ + "arithmeticDivide\0" /* 659 */ + "arithmeticIntDivide\0" /* 676 */ + "getIntegerNumbers\0" /* 696 */ + "arithmeticModulo\0" /* 714 */ + "exponent\0" /* 731 */ + "arithmeticPower\0" /* 740 */ + "powerMember\0" /* 756 */ + "getMembers\0" /* 768 */ + "toNumberSetOfNumber\0" /* 779 */ + "isOnlyFloats\0" /* 799 */ + "isOnlyFloatNumbers\0" /* 812 */ + "isOnlyInteger\0" /* 831 */ + "createTypeOfNumbers\0" /* 845 */ + "arithmeticNegate\0" /* 865 */ + "bitwiseAnd\0" /* 882 */ + "bitwiseOr\0" /* 893 */ + "bitwiseXor\0" /* 903 */ + "bitwiseShiftLeft\0" /* 914 */ + "bitwiseShiftRight\0" /* 931 */ + "operator\0" /* 949 */ + "calculateShift\0" /* 958 */ + "generalizeTooManyConstants\0" /* 973 */ + "generalize\0" /* 1000 */ + "invalidOrFold\0" /* 1011 */ + "calculateCommonMath\0" /* 1025 */ + "toNumberType\0" /* 1045 */ + "PHPStan\\TrinaryLogic\0" /* 1058 */ + "isArray\0" /* 1079 */ + "isOnlyNonEmptyArrays\0" /* 1087 */ + "calculateBitwise\0" /* 1108 */ + "getResultOfOperands\0" /* 1125 */ + "getZeroDivisorError\0" /* 1145 */ + "result\0" /* 1165 */ + "toResult\0" /* 1172 */ + "fold\0" /* 1181 */ + "left\0" /* 1186 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 1191 */ + "foldValues\0" /* 1223 */ + "base\0" /* 1234 */ + "foldPower\0" /* 1239 */ + "pow\0" /* 1249 */ + "numberType\0" /* 1253 */ + "getConstantNumber\0" /* 1264 */ + "integerType\0" /* 1282 */ + "getConstantInteger\0" /* 1294 */ + "expandIntegerIntervals\0" /* 1313 */ + "getFiniteScalarTypes\0" /* 1336 */ + "withoutArrays\0" /* 1357 */ + "integers\0" /* 1371 */ + "toOperandNumbers\0" /* 1380 */ + "toIntegerNumbers\0" /* 1397 */ + "isOnlyOneIntegerInterval\0" /* 1414 */ + "isOnlyIntegers\0" /* 1439 */ + "isOnlyStrings\0" /* 1454 */ + "mayBeString\0" /* 1468 */ + "mayBeArray\0" /* 1480 */ + "mayBeNonArray\0" /* 1491 */ + "mayBeNonNumber\0" /* 1505 */ + "unionTypes\0" /* 1520 */ + "getNumberValues\0" /* 1531 */ + "getTypeLooselyEqualTo\0" /* 1547 */ + "getLooselyEqualNumber\0" /* 1569 */ + "number\0" /* 1591 */ + "getNumberString\0" /* 1598 */ + "getNumberPartnerType\0" /* 1614 */ + "canBeZero\0" /* 1635 */ + "canBeNonZero\0" /* 1645 */ + "getBooleanAndNullPartnerType\0" /* 1658 */ + "getConstantStringPartnerType\0" /* 1687 */ + "getStringPartnerType\0" /* 1716 */ + "empty\0" /* 1737 */ + "nonEmpty\0" /* 1743 */ + "getArrayPartnerType\0" /* 1752 */ + "createNumberType\0" /* 1772 */ + "min\0" /* 1789 */ + "max\0" /* 1793 */ + "getIntegerTypeInInterval\0" /* 1797 */ + "createNumberLikeObjectType\0" /* 1822 */ + "createStringableObjectType\0" /* 1849 */ + "createNumericStringType"; /* 1876 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, MAY_BE_BOOL), /* __construct $unknown */ + reg::packed(8, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $null */ + reg::packed(13, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $false */ + reg::packed(19, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $true */ + reg::packed(24, MAY_BE_NULL, 32, false, false, 8), /* __construct $numbers */ + reg::packed(55, MAY_BE_ARRAY, reg::NoString, false, false, 71), /* __construct $constantStrings */ + reg::packed(74, MAY_BE_NULL, 82, false, false, 8), /* __construct $strings */ + reg::packed(100, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $emptyArray */ + reg::packed(111, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $nonEmptyArray */ + reg::packed(125, MAY_BE_NULL, 82, false, false, 8), /* __construct $arrays */ + reg::packed(132, MAY_BE_NULL, 82, false, false, 8), /* __construct $objects */ + reg::packed(140, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $resources */ + reg::packed(150, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $invalid */ + reg::packed(158, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $benevolent */ + reg::packed(169, MAY_BE_NULL, 175, false, false, 8), /* __construct $never */ + reg::packed(198, MAY_BE_NULL, 82, false, false, 8), /* __construct $unknownType */ + reg::packed(222, MAY_BE_NULL, 227, false, false, 8), /* createUnknown $type */ + reg::packed(250, 0, 251), /* createUnknown return */ + reg::packed(250, MAY_BE_BOOL), /* isPlusable return */ + reg::packed(250, MAY_BE_BOOL), /* isUnknown return */ + reg::packed(250, 0, 251), /* createEmpty return */ + reg::packed(250, 0, 251), /* createInvalid return */ + reg::packed(169, 0, 175), /* createNever $never */ + reg::packed(250, 0, 251), /* createNever return */ + reg::packed(250, 0, 251), /* createNull return */ + reg::packed(13, MAY_BE_BOOL), /* createBooleans $false */ + reg::packed(19, MAY_BE_BOOL), /* createBooleans $true */ + reg::packed(250, 0, 251), /* createBooleans return */ + reg::packed(24, 0, 32), /* createNumbers $numbers */ + reg::packed(250, 0, 251), /* createNumbers return */ + reg::packed(386, MAY_BE_STRING), /* createConstantString $value */ + reg::packed(250, 0, 251), /* createConstantString return */ + reg::packed(74, 0, 82), /* createStrings $strings */ + reg::packed(250, 0, 251), /* createStrings return */ + reg::packed(125, 0, 82), /* createArrays $arrays */ + reg::packed(250, 0, 251), /* createArrays return */ + reg::packed(132, 0, 82), /* createObjects $objects */ + reg::packed(250, 0, 251), /* createObjects return */ + reg::packed(250, 0, 251), /* createResources return */ + reg::packed(250, 0, 251), /* toBenevolent return */ + reg::packed(250, MAY_BE_BOOL), /* isEmpty return */ + reg::packed(491, 0, 251), /* union $other */ + reg::packed(250, 0, 251), /* union return */ + reg::packed(491, 0, 251), /* intersect $other */ + reg::packed(250, 0, 251), /* intersect return */ + reg::packed(513, 0, 524), /* getPossiblyLooselyEqualType $phpVersion */ + reg::packed(250, 0, 82), /* getPossiblyLooselyEqualType return */ + reg::packed(513, 0, 524), /* getAlwaysLooselyEqualType $phpVersion */ + reg::packed(250, 0, 82), /* getAlwaysLooselyEqualType return */ + reg::packed(601, 0, 251), /* arithmeticAdd $right */ + reg::packed(513, 0, 524), /* arithmeticAdd $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticAdd return */ + reg::packed(601, 0, 251), /* arithmeticSubtract $right */ + reg::packed(513, 0, 524), /* arithmeticSubtract $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticSubtract return */ + reg::packed(601, 0, 251), /* arithmeticMultiply $right */ + reg::packed(513, 0, 524), /* arithmeticMultiply $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticMultiply return */ + reg::packed(601, 0, 251), /* arithmeticDivide $right */ + reg::packed(513, 0, 524), /* arithmeticDivide $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticDivide return */ + reg::packed(601, 0, 251), /* arithmeticIntDivide $right */ + reg::packed(513, 0, 524), /* arithmeticIntDivide $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticIntDivide return */ + reg::packed(250, MAY_BE_NULL, 32), /* getIntegerNumbers return */ + reg::packed(601, 0, 251), /* arithmeticModulo $right */ + reg::packed(513, 0, 524), /* arithmeticModulo $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticModulo return */ + reg::packed(731, 0, 251), /* arithmeticPower $exponent */ + reg::packed(513, 0, 524), /* arithmeticPower $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticPower return */ + reg::packed(731, 0, 251), /* powerMember $exponent */ + reg::packed(513, 0, 524), /* powerMember $phpVersion */ + reg::packed(250, 0, 82), /* powerMember return */ + reg::packed(250, MAY_BE_ARRAY), /* getMembers return */ + reg::packed(250, MAY_BE_NULL, 32), /* toNumberSetOfNumber return */ + reg::packed(24, MAY_BE_NULL, 32), /* isOnlyFloats $numbers */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyFloats return */ + reg::packed(24, 0, 32), /* isOnlyFloatNumbers $numbers */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyFloatNumbers return */ + reg::packed(24, 0, 32), /* isOnlyInteger $numbers */ + reg::packed(386, MAY_BE_LONG), /* isOnlyInteger $value */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyInteger return */ + reg::packed(24, 0, 32), /* createTypeOfNumbers $numbers */ + reg::packed(250, 0, 82), /* createTypeOfNumbers return */ + reg::packed(513, 0, 524), /* arithmeticNegate $phpVersion */ + reg::packed(250, 0, 82), /* arithmeticNegate return */ + reg::packed(601, 0, 251), /* bitwiseAnd $right */ + reg::packed(513, 0, 524), /* bitwiseAnd $phpVersion */ + reg::packed(250, 0, 82), /* bitwiseAnd return */ + reg::packed(601, 0, 251), /* bitwiseOr $right */ + reg::packed(513, 0, 524), /* bitwiseOr $phpVersion */ + reg::packed(250, 0, 82), /* bitwiseOr return */ + reg::packed(601, 0, 251), /* bitwiseXor $right */ + reg::packed(513, 0, 524), /* bitwiseXor $phpVersion */ + reg::packed(250, 0, 82), /* bitwiseXor return */ + reg::packed(601, 0, 251), /* bitwiseShiftLeft $right */ + reg::packed(513, 0, 524), /* bitwiseShiftLeft $phpVersion */ + reg::packed(250, 0, 82), /* bitwiseShiftLeft return */ + reg::packed(601, 0, 251), /* bitwiseShiftRight $right */ + reg::packed(513, 0, 524), /* bitwiseShiftRight $phpVersion */ + reg::packed(250, 0, 82), /* bitwiseShiftRight return */ + reg::packed(949, MAY_BE_STRING), /* calculateShift $operator */ + reg::packed(601, 0, 251), /* calculateShift $right */ + reg::packed(513, 0, 524), /* calculateShift $phpVersion */ + reg::packed(250, 0, 82), /* calculateShift return */ + reg::packed(601, 0, 251), /* generalizeTooManyConstants $right */ + reg::packed(250, MAY_BE_ARRAY), /* generalizeTooManyConstants return */ + reg::packed(250, 0, 251), /* generalize return */ + reg::packed(949, MAY_BE_STRING), /* invalidOrFold $operator */ + reg::packed(601, 0, 251), /* invalidOrFold $right */ + reg::packed(250, MAY_BE_NULL, 82), /* invalidOrFold return */ + reg::packed(949, MAY_BE_STRING), /* calculateCommonMath $operator */ + reg::packed(601, 0, 251), /* calculateCommonMath $right */ + reg::packed(513, 0, 524), /* calculateCommonMath $phpVersion */ + reg::packed(250, 0, 82), /* calculateCommonMath return */ + reg::packed(250, MAY_BE_NULL, 82), /* toNumberType return */ + reg::packed(250, 0, 1058), /* isArray return */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyNonEmptyArrays return */ + reg::packed(949, MAY_BE_STRING), /* calculateBitwise $operator */ + reg::packed(601, 0, 251), /* calculateBitwise $right */ + reg::packed(513, 0, 524), /* calculateBitwise $phpVersion */ + reg::packed(250, 0, 82), /* calculateBitwise return */ + reg::packed(601, 0, 251), /* getResultOfOperands $right */ + reg::packed(250, MAY_BE_NULL, 82), /* getResultOfOperands return */ + reg::packed(250, MAY_BE_NULL, 82), /* getZeroDivisorError return */ + reg::packed(601, 0, 251), /* toResult $right */ + reg::packed(1165, 0, 82), /* toResult $result */ + reg::packed(250, 0, 82), /* toResult return */ + reg::packed(949, MAY_BE_STRING), /* fold $operator */ + reg::packed(601, 0, 251), /* fold $right */ + reg::packed(250, MAY_BE_NULL, 82), /* fold return */ + reg::packed(949, MAY_BE_STRING), /* foldValues $operator */ + reg::packed(1186, 0, 1191), /* foldValues $left */ + reg::packed(601, 0, 1191), /* foldValues $right */ + reg::packed(250, MAY_BE_NULL, 82), /* foldValues return */ + reg::packed(1234, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $base */ + reg::packed(731, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $exponent */ + reg::packed(250, MAY_BE_NULL, 82), /* foldPower return */ + reg::packed(1234, MAY_BE_ANY), /* pow $base */ + reg::packed(731, MAY_BE_ANY), /* pow $exponent */ + reg::packed(250, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* pow return */ + reg::packed(1253, 0, 82), /* getConstantNumber $numberType */ + reg::packed(250, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getConstantNumber return */ + reg::packed(1282, 0, 82), /* getConstantInteger $integerType */ + reg::packed(250, MAY_BE_NULL | MAY_BE_LONG), /* getConstantInteger return */ + reg::packed(1313, MAY_BE_BOOL), /* getFiniteScalarTypes $expandIntegerIntervals */ + reg::packed(250, MAY_BE_NULL | MAY_BE_ARRAY), /* getFiniteScalarTypes return */ + reg::packed(1357, MAY_BE_BOOL), /* toOperandNumbers $withoutArrays */ + reg::packed(1371, MAY_BE_BOOL), /* toOperandNumbers $integers */ + reg::packed(250, MAY_BE_NULL, 32), /* toOperandNumbers return */ + reg::packed(24, 0, 32), /* toIntegerNumbers $numbers */ + reg::packed(250, 0, 32), /* toIntegerNumbers return */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyOneIntegerInterval return */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyIntegers return */ + reg::packed(250, MAY_BE_BOOL), /* isOnlyStrings return */ + reg::packed(250, MAY_BE_BOOL), /* mayBeString return */ + reg::packed(250, MAY_BE_BOOL), /* mayBeArray return */ + reg::packed(250, MAY_BE_BOOL), /* mayBeNonArray return */ + reg::packed(250, MAY_BE_BOOL), /* mayBeNonNumber return */ + reg::packed(1186, MAY_BE_NULL, 82), /* unionTypes $left */ + reg::packed(601, MAY_BE_NULL, 82), /* unionTypes $right */ + reg::packed(250, MAY_BE_NULL, 82), /* unionTypes return */ + reg::packed(24, 0, 32), /* getNumberValues $numbers */ + reg::packed(250, MAY_BE_NULL | MAY_BE_ARRAY), /* getNumberValues return */ + reg::packed(386, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getTypeLooselyEqualTo $value */ + reg::packed(513, 0, 524), /* getTypeLooselyEqualTo $phpVersion */ + reg::packed(250, 0, 82), /* getTypeLooselyEqualTo return */ + reg::packed(386, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getLooselyEqualNumber $value */ + reg::packed(513, 0, 524), /* getLooselyEqualNumber $phpVersion */ + reg::packed(250, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getLooselyEqualNumber return */ + reg::packed(1591, MAY_BE_LONG | MAY_BE_DOUBLE), /* getNumberString $number */ + reg::packed(250, MAY_BE_STRING), /* getNumberString return */ + reg::packed(24, 0, 32), /* getNumberPartnerType $numbers */ + reg::packed(513, 0, 524), /* getNumberPartnerType $phpVersion */ + reg::packed(250, 0, 82), /* getNumberPartnerType return */ + reg::packed(24, 0, 32), /* canBeZero $numbers */ + reg::packed(250, MAY_BE_BOOL), /* canBeZero return */ + reg::packed(24, 0, 32), /* canBeNonZero $numbers */ + reg::packed(250, MAY_BE_BOOL), /* canBeNonZero return */ + reg::packed(1635, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeZero */ + reg::packed(1645, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeNonZero */ + reg::packed(250, 0, 82), /* getBooleanAndNullPartnerType return */ + reg::packed(386, MAY_BE_STRING), /* getConstantStringPartnerType $value */ + reg::packed(513, 0, 524), /* getConstantStringPartnerType $phpVersion */ + reg::packed(250, 0, 82), /* getConstantStringPartnerType return */ + reg::packed(74, 0, 82), /* getStringPartnerType $strings */ + reg::packed(513, 0, 524), /* getStringPartnerType $phpVersion */ + reg::packed(250, 0, 82), /* getStringPartnerType return */ + reg::packed(1737, MAY_BE_BOOL), /* getArrayPartnerType $empty */ + reg::packed(1743, MAY_BE_BOOL), /* getArrayPartnerType $nonEmpty */ + reg::packed(250, 0, 82), /* getArrayPartnerType return */ + reg::packed(1591, MAY_BE_LONG | MAY_BE_DOUBLE), /* createNumberType $number */ + reg::packed(250, 0, 82), /* createNumberType return */ + reg::packed(1789, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $min */ + reg::packed(1793, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $max */ + reg::packed(250, 0, 82), /* getIntegerTypeInInterval return */ + reg::packed(513, 0, 524), /* createNumberLikeObjectType $phpVersion */ + reg::packed(250, 0, 82), /* createNumberLikeObjectType return */ + reg::packed(513, 0, 524), /* createStringableObjectType $phpVersion */ + reg::packed(250, 0, 82), /* createStringableObjectType return */ + reg::packed(250, 0, 82), /* createNumericStringType return */ +}; +using Sig = reg::Sig; +} // namespace sigtab + +/* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ +namespace sig { +inline constexpr sigtab::Sig __construct = { { 210 /* __construct */, 1, 0, 16, reg::NoArg, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig createUnknown = { { 273 /* createUnknown */, 0, 16, 1, 17, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isPlusable = { { 287 /* isPlusable */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUnknown = { { 298 /* isUnknown */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig createEmpty = { { 308 /* createEmpty */, 0, 20, 0, 20, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createInvalid = { { 320 /* createInvalid */, 0, 21, 0, 21, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNever = { { 334 /* createNever */, 1, 22, 1, 23, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNull = { { 346 /* createNull */, 0, 24, 0, 24, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createBooleans = { { 357 /* createBooleans */, 2, 25, 2, 27, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumbers = { { 372 /* createNumbers */, 1, 28, 1, 29, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createConstantString = { { 392 /* createConstantString */, 1, 30, 1, 31, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createStrings = { { 413 /* createStrings */, 1, 32, 1, 33, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createArrays = { { 427 /* createArrays */, 1, 34, 1, 35, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createObjects = { { 440 /* createObjects */, 1, 36, 1, 37, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createResources = { { 454 /* createResources */, 0, 38, 0, 38, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toBenevolent = { { 470 /* toBenevolent */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isEmpty = { { 483 /* isEmpty */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig union_ = { { 497 /* union */, 1, 41, 1, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig intersect = { { 503 /* intersect */, 1, 43, 1, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 547 /* getPossiblyLooselyEqualType */, 1, 45, 1, 46, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 575 /* getAlwaysLooselyEqualType */, 1, 47, 1, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticAdd = { { 607 /* arithmeticAdd */, 2, 49, 2, 51, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticSubtract = { { 621 /* arithmeticSubtract */, 2, 52, 2, 54, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticMultiply = { { 640 /* arithmeticMultiply */, 2, 55, 2, 57, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticDivide = { { 659 /* arithmeticDivide */, 2, 58, 2, 60, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticIntDivide = { { 676 /* arithmeticIntDivide */, 2, 61, 2, 63, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getIntegerNumbers = { { 696 /* getIntegerNumbers */, 0, 64, 0, 64, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig arithmeticModulo = { { 714 /* arithmeticModulo */, 2, 65, 2, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 740 /* arithmeticPower */, 2, 68, 2, 70, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig powerMember = { { 756 /* powerMember */, 2, 71, 2, 73, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getMembers = { { 768 /* getMembers */, 0, 74, 0, 74, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toNumberSetOfNumber = { { 779 /* toNumberSetOfNumber */, 0, 75, 0, 75, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyFloats = { { 799 /* isOnlyFloats */, 1, 76, 1, 77, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyFloatNumbers = { { 812 /* isOnlyFloatNumbers */, 1, 78, 1, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyInteger = { { 831 /* isOnlyInteger */, 2, 80, 2, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createTypeOfNumbers = { { 845 /* createTypeOfNumbers */, 1, 83, 1, 84, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arithmeticNegate = { { 865 /* arithmeticNegate */, 1, 85, 1, 86, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 882 /* bitwiseAnd */, 2, 87, 2, 89, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 893 /* bitwiseOr */, 2, 90, 2, 92, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 903 /* bitwiseXor */, 2, 93, 2, 95, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftLeft = { { 914 /* bitwiseShiftLeft */, 2, 96, 2, 98, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftRight = { { 931 /* bitwiseShiftRight */, 2, 99, 2, 101, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculateShift = { { 958 /* calculateShift */, 3, 102, 3, 105, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeTooManyConstants = { { 973 /* generalizeTooManyConstants */, 1, 106, 1, 107, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalize = { { 1000 /* generalize */, 0, 108, 0, 108, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invalidOrFold = { { 1011 /* invalidOrFold */, 2, 109, 2, 111, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig calculateCommonMath = { { 1025 /* calculateCommonMath */, 3, 112, 3, 115, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toNumberType = { { 1045 /* toNumberType */, 0, 116, 0, 116, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isArray = { { 1079 /* isArray */, 0, 117, 0, 117, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyNonEmptyArrays = { { 1087 /* isOnlyNonEmptyArrays */, 0, 118, 0, 118, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig calculateBitwise = { { 1108 /* calculateBitwise */, 3, 119, 3, 122, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getResultOfOperands = { { 1125 /* getResultOfOperands */, 1, 123, 1, 124, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getZeroDivisorError = { { 1145 /* getZeroDivisorError */, 0, 125, 0, 125, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toResult = { { 1172 /* toResult */, 2, 126, 2, 128, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig fold = { { 1181 /* fold */, 2, 129, 2, 131, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig foldValues = { { 1223 /* foldValues */, 3, 132, 3, 135, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig foldPower = { { 1239 /* foldPower */, 2, 136, 2, 138, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig pow = { { 1249 /* pow */, 2, 139, 2, 141, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantNumber = { { 1264 /* getConstantNumber */, 1, 142, 1, 143, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantInteger = { { 1294 /* getConstantInteger */, 1, 144, 1, 145, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFiniteScalarTypes = { { 1336 /* getFiniteScalarTypes */, 1, 146, 1, 147, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toOperandNumbers = { { 1380 /* toOperandNumbers */, 2, 148, 2, 150, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toIntegerNumbers = { { 1397 /* toIntegerNumbers */, 1, 151, 1, 152, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyOneIntegerInterval = { { 1414 /* isOnlyOneIntegerInterval */, 0, 153, 0, 153, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyIntegers = { { 1439 /* isOnlyIntegers */, 0, 154, 0, 154, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyStrings = { { 1454 /* isOnlyStrings */, 0, 155, 0, 155, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeString = { { 1468 /* mayBeString */, 0, 156, 0, 156, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeArray = { { 1480 /* mayBeArray */, 0, 157, 0, 157, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeNonArray = { { 1491 /* mayBeNonArray */, 0, 158, 0, 158, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeNonNumber = { { 1505 /* mayBeNonNumber */, 0, 159, 0, 159, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig unionTypes = { { 1520 /* unionTypes */, 2, 160, 2, 162, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberValues = { { 1531 /* getNumberValues */, 1, 163, 1, 164, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getTypeLooselyEqualTo = { { 1547 /* getTypeLooselyEqualTo */, 2, 165, 2, 167, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getLooselyEqualNumber = { { 1569 /* getLooselyEqualNumber */, 2, 168, 2, 170, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberString = { { 1598 /* getNumberString */, 1, 171, 1, 172, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberPartnerType = { { 1614 /* getNumberPartnerType */, 2, 173, 2, 175, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canBeZero = { { 1635 /* canBeZero */, 1, 176, 1, 177, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canBeNonZero = { { 1645 /* canBeNonZero */, 1, 178, 1, 179, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getBooleanAndNullPartnerType = { { 1658 /* getBooleanAndNullPartnerType */, 2, 180, 2, 182, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantStringPartnerType = { { 1687 /* getConstantStringPartnerType */, 2, 183, 2, 185, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getStringPartnerType = { { 1716 /* getStringPartnerType */, 2, 186, 2, 188, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getArrayPartnerType = { { 1752 /* getArrayPartnerType */, 2, 189, 2, 191, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumberType = { { 1772 /* createNumberType */, 1, 192, 1, 193, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerTypeInInterval = { { 1797 /* getIntegerTypeInInterval */, 2, 194, 2, 196, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumberLikeObjectType = { { 1822 /* createNumberLikeObjectType */, 1, 197, 1, 198, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createStringableObjectType = { { 1849 /* createStringableObjectType */, 1, 199, 1, 200, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumericStringType = { { 1876 /* createNumericStringType */, 0, 201, 0, 201, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +} // namespace sig + +} // namespace ptdecl::ValueSet + +#endif diff --git a/turbo-ext/src/support.cpp b/turbo-ext/src/support.cpp index 7d8c72d5909..5cb546e775c 100644 --- a/turbo-ext/src/support.cpp +++ b/turbo-ext/src/support.cpp @@ -414,7 +414,6 @@ static const pt_class_template pt_class_templates[PT_CLASS_COUNT] = { /* PT_CLASS_NATIVE_PHP_TYPE_NODE_HELPER */ {"nativePhpTypeNodeHelper", "PHPStan\\Type\\NativePhpTypeNodeHelper"}, /* PT_CLASS_FULLY_QUALIFIED_NAME_NODE */ {"fullyQualifiedNameNode", "PHPStan\\PhpDocParser\\Ast\\Type\\FullyQualifiedNameNode"}, /* PT_CLASS_TYPE_NODE_DESCRIBER */ {"typeNodeDescriber", "PHPStan\\Type\\TypeNodeDescriber"}, - /* PT_CLASS_VALUE_SET */ {"valueSet", "PHPStan\\Type\\ValueSet"}, }; zend_class_entry *pt_class(int idx) diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 640e2b1a9b6..f49ff208f79 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -518,8 +518,6 @@ enum { /* the class names of Type::toPhpDocNode() and how describe() prints them */ PT_CLASS_FULLY_QUALIFIED_NAME_NODE, PT_CLASS_TYPE_NODE_DESCRIBER, - /* the values a type holds, Type::toValueSet() */ - PT_CLASS_VALUE_SET, PT_CLASS_COUNT }; diff --git a/turbo-ext/tests/initializer-escape.php b/turbo-ext/tests/initializer-escape.php index 52a3d309214..c707d004409 100644 --- a/turbo-ext/tests/initializer-escape.php +++ b/turbo-ext/tests/initializer-escape.php @@ -109,15 +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 native int's arithmeticSubtract() meets the twin's - // 3 in OperatorTypeHelper) - the type callback was kept before that - // the native operators compute on the PHP ValueSet, which the prefix - // hands the twins' NumberSet and MixedType (UnaryMinusHandler, - // BinaryOpHandler) - the type callback was kept before that too + // through either (the native int's arithmeticSubtract() meets the value + // set of the twin's 3) - 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') || str_contains($e->getMessage(), 'PHPStanTurbo\\VerbosityLevel given'))) - || (in_array($label, ['UnaryMinusHandler', 'BinaryOpHandler'], true) && str_contains($e->getMessage(), 'PHPStanTurbo\\NumberSet given')), + || ($label === 'BinaryOpHandler' && (str_contains($e->getMessage(), 'PHPStanTurbo\\TrinaryLogic returned') || str_contains($e->getMessage(), 'PHPStanTurbo\\VerbosityLevel given') || str_contains($e->getMessage(), 'PHPStan\\Type\\ValueSet given'))), "escaping getTypeCallback ($label): " . get_class($e) . ': ' . $e->getMessage(), ); } catch (\Throwable $e) { @@ -135,16 +131,7 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC $ieVarResult = $ieNodeScopeResolver->processExprNode($ieStmt, $ieVarExpr, $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()); foreach ([true, false] as $increment) { $ieTypeCallback = (new \PHPStanTurbo\IncDecTypeHelper($ieKeeping))->getTypeCallback($ieVarExpr, $ieVarResult, $increment); - foreach ([false, true] as $native) { - try { - check(is_string($ieDescribe($ieTypeCallback($native))), 'escaping getTypeCallback (IncDecTypeHelper): the callback resolves'); - } catch (\TypeError $e) { - // the native int computes `$a + 1` on the PHP ValueSet, which the - // prefix hands the twin's NumberSet - the type callback was kept - // before that - check(str_contains($e->getMessage(), 'PHPStanTurbo\\NumberSet given'), 'escaping getTypeCallback (IncDecTypeHelper): ' . $e->getMessage()); - } - } + check(is_string($ieDescribe($ieTypeCallback(false))) && is_string($ieDescribe($ieTypeCallback(true))), 'escaping getTypeCallback (IncDecTypeHelper): the callback resolves'); } } finally { $ieScope->popExpressionResultStorage(); @@ -160,8 +147,7 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC unset($storage, $ieVarResult, $ieTypeCallback); gc_collect_cycles(); $ieDeepStack(200); -// UnaryMinusHandler stops at the PHP ValueSet before it asks for the native type -check(count($ieKeeping->kept) === 14, 'escaping getTypeCallback: the keeping resolver was handed the callbacks (' . count($ieKeeping->kept) . ')'); +check(count($ieKeeping->kept) === 15, 'escaping getTypeCallback: the keeping resolver was handed the callbacks (' . count($ieKeeping->kept) . ')'); foreach ($ieKeeping->kept as $i => [$method, $callback, $asked, $answered]) { $ieDeepStack(50); try { @@ -215,20 +201,14 @@ public function callUnaryOperatorTypeSpecifyingExtensions(string $operatorSigil, $ieOversized = new \PhpParser\Node\Expr\Array_(array_map(static fn (int $i) => new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Scalar\Int_($i)), range(0, 300))); $ieContext = \PHPStan\Reflection\InitializerExprContext::fromClass(\PHPStan\TrinaryLogic::class, null); $ieResolver->getType($ieOversized, $ieContext); -try { - $ieResolver->getType(new \PhpParser\Node\Expr\BinaryOp\Plus(new \PhpParser\Node\Scalar\Int_(1), new \PhpParser\Node\Expr\UnaryMinus($ieOversized)), $ieContext); -} catch (\TypeError $e) { - // the negation computes on the PHP ValueSet, which the prefix hands the - // twin's MixedType - the builder kept the callable before that - check(str_contains($e->getMessage(), 'PHPStanTurbo\\MixedType given'), 'escaping getTypeCallback (OversizedArrayBuilder): ' . $e->getMessage()); -} +$ieResolver->getType(new \PhpParser\Node\Expr\BinaryOp\Plus(new \PhpParser\Node\Scalar\Int_(1), new \PhpParser\Node\Expr\UnaryMinus($ieOversized)), $ieContext); $iePhpCallable = static fn (\PhpParser\Node\Expr $expr): \PHPStan\Type\Type => new \PHPStanTurbo\IntegerType(); $ieResolver->getArrayType($ieOversized, $iePhpCallable); check(count($ieBuilder->kept) === 3 && $ieBuilder->kept[2] === $iePhpCallable, 'escaping getTypeCallback (OversizedArrayBuilder): the builder was handed the callables, the PHP one itself'); $ieAsked = [ new \PhpParser\Node\Scalar\Int_(7), new \PhpParser\Node\Scalar\MagicConst\Class_(), - new \PhpParser\Node\Expr\BinaryOp\Concat(new \PhpParser\Node\Scalar\Int_(6), new \PhpParser\Node\Scalar\Int_(7)), + new \PhpParser\Node\Expr\BinaryOp\Mul(new \PhpParser\Node\Scalar\Int_(6), new \PhpParser\Node\Scalar\Int_(7)), new \PhpParser\Node\Expr\Array_([new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Scalar\String_('x'), new \PhpParser\Node\Scalar\String_('k'))]), ]; $ieExpected = array_map(static fn ($expr) => $ieDescribe($ieResolver->getType($expr, $ieContext)), $ieAsked); diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index c8985a3dfef..b60b91f1445 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -1120,6 +1120,7 @@ public function enterNode(\PhpParser\Node $node) $covered[\PHPStan\Reflection\Assertions::class] = true; $covered[\PHPStan\Reflection\InitializerExprTypeResolver::class] = true; $covered[\PHPStan\Type\OperatorTypeHelper::class] = true; +$covered[\PHPStan\Type\ValueSet::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'); @@ -1192,7 +1193,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\FloatRangeType::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) { +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\FloatRangeType::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\Type\ValueSet::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 395d7c97396..1d682b64880 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7792,6 +7792,7 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu } $observations['native PHPStan\Reflection\InitializerExprTypeResolver'] = (new ReflectionMethod(\PHPStan\Reflection\InitializerExprTypeResolver::class, 'getType'))->isInternal(); $observations['native PHPStan\Type\OperatorTypeHelper'] = (new ReflectionMethod(\PHPStan\Type\OperatorTypeHelper::class, 'concat'))->isInternal(); +$observations['native PHPStan\Type\ValueSet'] = (new ReflectionMethod(\PHPStan\Type\ValueSet::class, 'union'))->isInternal(); { $resolver = $stringContainer->getByType(\PHPStan\Reflection\InitializerExprTypeResolver::class); $r = []; @@ -7921,6 +7922,22 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu } } } + // the ValueSet of every operand: its loose equality, and its union and intersection with the others + foreach ([70400, 80000, 80500] as $looseVersionId) { + $looseVersion = new \PHPStan\Php\PhpVersion($looseVersionId); + foreach ($operands + $integerOperands + $numericStringOperands as $name => $operand) { + $r["valueSet possiblyLooselyEqual $looseVersionId $name"] = $catching(static fn () => $operand->toValueSet()->getPossiblyLooselyEqualType($looseVersion)); + $r["valueSet alwaysLooselyEqual $looseVersionId $name"] = $catching(static fn () => $operand->toValueSet()->getAlwaysLooselyEqualType($looseVersion)); + } + } + foreach ($operands as $leftName => $left) { + $leftSet = $left->toValueSet(); + $r["valueSet flags $leftName"] = [$leftSet->isEmpty(), $leftSet->isUnknown(), $leftSet->isPlusable(), $leftSet->toBenevolent()->isEmpty()]; + foreach ($operands as $rightName => $right) { + $r["valueSet union $leftName $rightName"] = $catching(static fn () => $leftSet->union($right->toValueSet())->getPossiblyLooselyEqualType(new \PHPStan\Php\PhpVersion(80500))); + $r["valueSet intersect $leftName $rightName"] = $catching(static fn () => $leftSet->intersect($right->toValueSet())->getPossiblyLooselyEqualType(new \PHPStan\Php\PhpVersion(80500))); + } + } foreach ($operands as $name => $operand) { $r["exponentiate $name int7"] = $catching(static fn () => $operand->exponentiate($int(7))); } From da7bfa36d14973d0ffc2b311b7bedced5b6ff542 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 07:13:57 +0000 Subject: [PATCH 06/15] Compute concatenation on StringSet ValueSet keeps its strings as a StringSet: the constant strings plus the type of the other strings. The . operator concatenates the strings the values cast to, and the strings of numbers keep what their type cannot say: they never end in whitespace, and those of non-negative integers are digits only. A numeric left side thus stays numeric after digits whatever produced them, and after a decimal integer string a fraction or exponent keeps it numeric. A never operand gives never, as it does for the arithmetic operators. The native mirror is StringSet.cpp. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- phpstan-baseline.neon | 6 - src/Type/Constant/ConstantStringType.php | 2 +- src/Type/OperatorTypeHelper.php | 159 +--- src/Type/StringSet.php | 363 ++++++++ src/Type/Traits/OperatorTypeTrait.php | 2 +- src/Type/ValueSet.php | 195 ++-- tests/PHPStan/Analyser/nsrt/bcmath-number.php | 2 +- .../TypesAssignedToPropertiesRuleTest.php | 8 - turbo-ext/src/ConstantStringType.cpp | 7 +- turbo-ext/src/OperatorTypeHelper.cpp | 258 +----- turbo-ext/src/StringSet.cpp | 614 +++++++++++++ turbo-ext/src/TypeTraits.cpp | 2 +- turbo-ext/src/TypeTraits.h | 15 + turbo-ext/src/ValueSet.cpp | 237 +++-- turbo-ext/src/generated/OperatorTypeHelper.h | 49 +- turbo-ext/src/generated/StringSet.h | 153 ++++ turbo-ext/src/generated/ValueSet.h | 841 +++++++++--------- turbo-ext/src/support.h | 3 +- turbo-ext/tests/smoke.php | 3 +- turbo-ext/tests/type-family.php | 31 +- 20 files changed, 1904 insertions(+), 1046 deletions(-) create mode 100644 src/Type/StringSet.php create mode 100644 turbo-ext/src/StringSet.cpp create mode 100644 turbo-ext/src/generated/StringSet.h diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8494de0fcef..14f91cdd161 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1453,12 +1453,6 @@ parameters: count: 4 path: src/Type/ObjectWithoutClassType.php - - - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' - identifier: phpstanApi.instanceofType - count: 4 - 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/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 8ca1026ad50..3152bf39d5a 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -659,7 +659,7 @@ public function toPhpDocNode(): TypeNode public function toValueSet(): ValueSet { - return ValueSet::createConstantString($this->getValue()); + return ValueSet::createConstantString($this); } } diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php index ff1edb3948c..31d826050ab 100644 --- a/src/Type/OperatorTypeHelper.php +++ b/src/Type/OperatorTypeHelper.php @@ -2,178 +2,21 @@ namespace PHPStan\Type; -use Nette\Utils\Strings; use PHPStan\Php\PhpVersion; -use PHPStan\Reflection\InitializerExprTypeResolver; 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\Constant\ConstantArrayTypeBuilder; -use PHPStan\Type\Constant\ConstantStringType; -use function array_merge; use function count; -use function is_numeric; /** - * The operators of Type::stringConcatenate() and Type::arrayUnion() for the types that do not - * decide the result on their own. The arithmetic and bitwise operators are on ValueSet. + * The array + operator of Type::arrayUnion(). The other operators are on ValueSet. */ #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/OperatorTypeHelper.cpp')] final class OperatorTypeHelper { - public static function concat(Type $left, Type $right, PhpVersion $phpVersion): 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 <= 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() - // '' . '' 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 - $validationCallback = $left->isInteger()->yes() - ? 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) { - 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(); - } - - /** - * 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 arrayUnion(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type { return self::tryArrayUnion($leftType, $rightType) ?? new ErrorType(); diff --git a/src/Type/StringSet.php b/src/Type/StringSet.php new file mode 100644 index 00000000000..272a93f27fb --- /dev/null +++ b/src/Type/StringSet.php @@ -0,0 +1,363 @@ + $constants + */ + private function __construct( + private array $constants, + private ?Type $type, + private bool $digits, + private bool $whitespaceFree, + ) + { + } + + public static function createEmpty(): self + { + return new self([], null, false, false); + } + + public static function createConstant(ConstantStringType $string): self + { + return new self([$string], null, false, false); + } + + /** Strings of a string type: StringType, or an accessory such as AccessoryNumericStringType. */ + public static function createType(Type $strings): self + { + return new self([], $strings, false, $strings->isDecimalIntegerString()->yes()); + } + + /** The same strings, cast from numbers: of non-negative integers only when $digits. */ + public function toNumberStrings(bool $digits): self + { + return new self($this->constants, $this->type, $digits, true); + } + + /** @return list */ + public function getConstantStrings(): array + { + return $this->constants; + } + + /** The strings that are not constants, null when there are none. */ + public function getType(): ?Type + { + return $this->type; + } + + public function isEmpty(): bool + { + return $this->constants === [] && $this->type === null; + } + + public function union(self $other): self + { + $constants = $this->constants; + foreach ($other->constants as $constant) { + if (self::contains($constants, $constant->getValue())) { + continue; + } + + $constants[] = $constant; + } + + if ($this->type === null || $other->type === null) { + $typed = $this->type === null ? $other : $this; + + return new self($constants, $typed->type, $typed->digits, $typed->whitespaceFree); + } + + return new self( + $constants, + TypeCombinator::union($this->type, $other->type), + $this->digits && $other->digits, + $this->whitespaceFree && $other->whitespaceFree, + ); + } + + public function intersect(self $other): self + { + $constants = []; + foreach ([[$this, $other], [$other, $this]] as [$set, $otherSet]) { + foreach ($set->constants as $constant) { + if ( + self::contains($constants, $constant->getValue()) + || ( + !self::contains($otherSet->constants, $constant->getValue()) + && ($otherSet->type === null || $otherSet->type->isSuperTypeOf($constant)->no()) + ) + ) { + continue; + } + + $constants[] = $constant; + } + } + + if ($this->type === null || $other->type === null) { + return new self($constants, null, false, false); + } + + return new self( + $constants, + TypeCombinator::intersect($this->type, $other->type), + $this->digits || $other->digits, + $this->whitespaceFree || $other->whitespaceFree, + ); + } + + public function toType(): Type + { + $types = $this->constants; + if ($this->type !== null) { + $types[] = $this->type; + } + + return TypeCombinator::union(...$types); + } + + /** Models the . operator. */ + public function concatenate(self $right, PhpVersion $phpVersion): Type + { + if ($this->isEmpty() || $right->isEmpty()) { + return new NeverType(); + } + if ($this->isEmptyString()) { + return $right->toType(); + } + if ($right->isEmptyString()) { + return $this->toType(); + } + + // we limit the number of union-types for performance reasons + if ( + $this->type === null + && $right->type === null + && count($this->constants) * count($right->constants) <= InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT + ) { + $strings = []; + foreach ($this->constants as $leftConstant) { + if ($leftConstant->getValue() === '') { + foreach ($right->constants as $rightConstant) { + $strings[] = $rightConstant; + } + + continue; + } + + foreach ($right->constants as $rightConstant) { + $strings[] = $rightConstant->getValue() === '' ? $leftConstant : $leftConstant->append($rightConstant); + } + } + + return TypeCombinator::union(...$strings); + } + + $accessoryTypes = []; + if ($this->isNonEmpty() && $right->isNonEmpty()) { + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } elseif ($this->isNonFalsy() || $right->isNonFalsy()) { + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } elseif ($this->isNonEmpty() || $right->isNonEmpty()) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + + if ($this->isLiteral() && $right->isLiteral()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + + if ($this->isLowercase() && $right->isLowercase()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } + + if ($this->isUppercase() && $right->isUppercase()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } + + if ( + $this->isNumericOrEmpty($phpVersion) + // '' . '' is not numeric + && ($this->isNonEmpty() || $right->isNonEmpty()) + // '1 ' is numeric on PHP 8, '1 5' is not + && !$this->canEndInWhitespace($phpVersion) + && $right->isNumberSuffix($this->isDecimalInteger(), $phpVersion) + ) { + $accessoryTypes[] = new AccessoryNumericStringType(); + } + + if (count($accessoryTypes) > 0) { + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); + } + + return new StringType(); + } + + private function isEmptyString(): bool + { + return $this->type === null && count($this->constants) === 1 && $this->constants[0]->getValue() === ''; + } + + private function isNonEmpty(): bool + { + foreach ($this->constants as $constant) { + if ($constant->getValue() === '') { + return false; + } + } + + return $this->type === null || $this->type->isNonEmptyString()->yes(); + } + + private function isNonFalsy(): bool + { + foreach ($this->constants as $constant) { + if (in_array($constant->getValue(), ['', '0'], true)) { + return false; + } + } + + return $this->type === null || $this->type->isNonFalsyString()->yes(); + } + + private function isLiteral(): bool + { + return $this->type === null || $this->type->isLiteralString()->yes(); + } + + private function isLowercase(): bool + { + foreach ($this->constants as $constant) { + if (strtolower($constant->getValue()) !== $constant->getValue()) { + return false; + } + } + + return $this->type === null || $this->type->isLowercaseString()->yes(); + } + + private function isUppercase(): bool + { + foreach ($this->constants as $constant) { + if (strtoupper($constant->getValue()) !== $constant->getValue()) { + return false; + } + } + + return $this->type === null || $this->type->isUppercaseString()->yes(); + } + + private function isDecimalInteger(): bool + { + foreach ($this->constants as $constant) { + if ((string) (int) $constant->getValue() !== $constant->getValue()) { + return false; + } + } + + return $this->type === null || $this->type->isDecimalIntegerString()->yes(); + } + + /** Whether the strings other than '' are numeric. */ + private function isNumericOrEmpty(PhpVersion $phpVersion): bool + { + foreach ($this->constants as $constant) { + if ($constant->getValue() !== '' && !self::isNumeric($constant->getValue(), $phpVersion)) { + return false; + } + } + + return $this->type === null || $this->type->isNumericString()->yes(); + } + + /** Whether a string can end in whitespace, which PHP 8 allows in numeric strings and PHP 7 does not. */ + private function canEndInWhitespace(PhpVersion $phpVersion): bool + { + foreach ($this->constants as $constant) { + if (rtrim($constant->getValue(), self::WHITESPACE) !== $constant->getValue()) { + return true; + } + } + + return $this->type !== null && !$this->whitespaceFree && $phpVersion->allowsTrailingWhitespaceInNumericStrings(); + } + + /** + * Whether the strings keep a numeric string numeric when appended to it: digits, + * and after the digits of an integer also a fraction and an exponent. After them a + * sign or a whitespace is not numeric, and PHP 7 does not allow a trailing newline. + */ + private function isNumberSuffix(bool $afterInteger, PhpVersion $phpVersion): bool + { + foreach ($this->constants as $constant) { + $value = $constant->getValue(); + if ($value === '') { + continue; + } + + $pattern = $afterInteger ? '#^\d+(\.\d*)?([eE][+-]?\d+)?\z#' : '#^\d+\z#'; + if (!self::isNumeric($value, $phpVersion) || Strings::match($value, $pattern) === null) { + return false; + } + } + + return $this->type === null || $this->digits; + } + + /** is_numeric() of the PHP version, which PHP 8 extends to trailing whitespace. */ + private static function isNumeric(string $value, PhpVersion $phpVersion): bool + { + $trimmed = rtrim($value, self::WHITESPACE); + if ($trimmed !== $value && !$phpVersion->allowsTrailingWhitespaceInNumericStrings()) { + return false; + } + + return is_numeric($trimmed); + } + + /** @param list $constants */ + private static function contains(array $constants, string $value): bool + { + foreach ($constants as $constant) { + if ($constant->getValue() === $value) { + return true; + } + } + + return false; + } + +} diff --git a/src/Type/Traits/OperatorTypeTrait.php b/src/Type/Traits/OperatorTypeTrait.php index 34aaa56d227..075cb5d1cda 100644 --- a/src/Type/Traits/OperatorTypeTrait.php +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -122,7 +122,7 @@ public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type public function stringConcatenate(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::concat($this, $right, $phpVersion); + return $this->toValueSet()->stringConcatenate($right->toValueSet(), $phpVersion); } public function arrayUnion(Type $right, PhpVersion $phpVersion): Type diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php index 210394ec81b..1557a35b21c 100644 --- a/src/Type/ValueSet.php +++ b/src/Type/ValueSet.php @@ -38,9 +38,9 @@ * describes itself with Type::toValueSet(); unions and intersections of types are * unions and intersections of their sets. * - * Numbers are a NumberSet. Strings are a list of constant strings plus the type of - * the other strings, objects are kept as a type, because their class decides how - * they compare. A set of unknown values stands for a type that can hold anything. + * Numbers are a NumberSet, strings a StringSet. Objects are kept as a type, because + * their class decides how they compare. A set of unknown values stands for a type + * that can hold anything. */ #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ValueSet.cpp')] final class ValueSet @@ -53,17 +53,13 @@ final class ValueSet private const EXACT_FLOAT_DIGITS = 17; private const SHORT_FLOAT_DIGITS = 15; - /** - * @param list $constantStrings - */ private function __construct( private bool $unknown, private bool $null = false, private bool $false = false, private bool $true = false, private ?NumberSet $numbers = null, - private array $constantStrings = [], - private ?Type $strings = null, + private ?StringSet $strings = null, private bool $emptyArray = false, private bool $nonEmptyArray = false, private ?Type $arrays = null, @@ -127,15 +123,15 @@ public static function createNumbers(NumberSet $numbers): self return new self(false, numbers: $numbers); } - public static function createConstantString(string $value): self + public static function createConstantString(ConstantStringType $string): self { - return new self(false, constantStrings: [$value]); + return new self(false, strings: StringSet::createConstant($string)); } /** Strings of a string type: StringType, or an accessory such as AccessoryNumericStringType. */ public static function createStrings(Type $strings): self { - return new self(false, strings: $strings); + return new self(false, strings: StringSet::createType($strings)); } /** Arrays of an array type: ArrayType, ConstantArrayType or an accessory such as NonEmptyArrayType. */ @@ -172,7 +168,6 @@ public function isEmpty(): bool && !$this->false && !$this->true && ($this->numbers === null || $this->numbers->isEmpty()) - && $this->constantStrings === [] && $this->strings === null && !$this->emptyArray && !$this->nonEmptyArray @@ -193,23 +188,13 @@ public function union(self $other): self ); } - $constantStrings = $this->constantStrings; - foreach ($other->constantStrings as $constantString) { - if (in_array($constantString, $constantStrings, true)) { - continue; - } - - $constantStrings[] = $constantString; - } - return new self( false, $this->null || $other->null, $this->false || $other->false, $this->true || $other->true, $this->numbers === null || $other->numbers === null ? $this->numbers ?? $other->numbers : $this->numbers->union($other->numbers), - $constantStrings, - self::unionTypes($this->strings, $other->strings), + $this->strings === null || $other->strings === null ? $this->strings ?? $other->strings : $this->strings->union($other->strings), $this->emptyArray || $other->emptyArray, $this->nonEmptyArray || $other->nonEmptyArray, self::unionTypes($this->arrays, $other->arrays), @@ -229,22 +214,7 @@ public function intersect(self $other): self return $this; } - $constantStrings = []; - foreach ([[$this, $other], [$other, $this]] as [$set, $otherSet]) { - foreach ($set->constantStrings as $constantString) { - if ( - in_array($constantString, $constantStrings, true) - || ( - !in_array($constantString, $otherSet->constantStrings, true) - && ($otherSet->strings === null || $otherSet->strings->isSuperTypeOf(new ConstantStringType($constantString))->no()) - ) - ) { - continue; - } - - $constantStrings[] = $constantString; - } - } + $strings = $this->strings === null || $other->strings === null ? null : $this->strings->intersect($other->strings); return new self( false, @@ -252,8 +222,7 @@ public function intersect(self $other): self $this->false && $other->false, $this->true && $other->true, $this->numbers === null || $other->numbers === null ? null : $this->numbers->intersect($other->numbers), - $constantStrings, - $this->strings === null || $other->strings === null ? null : TypeCombinator::intersect($this->strings, $other->strings), + $strings === null || $strings->isEmpty() ? null : $strings, $this->emptyArray && $other->emptyArray, $this->nonEmptyArray && $other->nonEmptyArray, $this->arrays === null || $other->arrays === null ? null : TypeCombinator::intersect($this->arrays, $other->arrays), @@ -289,11 +258,12 @@ public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type $types[] = self::getNumberPartnerType($this->numbers, $phpVersion); $types[] = self::getBooleanAndNullPartnerType(self::canBeZero($this->numbers), self::canBeNonZero($this->numbers)); } - foreach ($this->constantStrings as $constantString) { - $types[] = self::getConstantStringPartnerType($constantString, $phpVersion); + foreach ($this->getConstantStrings() as $constantString) { + $types[] = self::getConstantStringPartnerType($constantString->getValue(), $phpVersion); } - if ($this->strings !== null) { - $types[] = self::getStringPartnerType($this->strings, $phpVersion); + $stringType = $this->strings?->getType(); + if ($stringType !== null) { + $types[] = self::getStringPartnerType($stringType, $phpVersion); } if ($this->emptyArray || $this->nonEmptyArray) { $types[] = self::getArrayPartnerType($this->emptyArray, $this->nonEmptyArray); @@ -317,7 +287,7 @@ public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type */ public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type { - if ($this->unknown || $this->strings !== null || $this->nonEmptyArray || $this->resources) { + if ($this->unknown || $this->strings?->getType() !== null || $this->nonEmptyArray || $this->resources) { return new NeverType(); } @@ -339,8 +309,8 @@ public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type return new NeverType(); } } - foreach ($this->constantStrings as $constantString) { - $types[] = self::getTypeLooselyEqualTo($constantString, $phpVersion); + foreach ($this->getConstantStrings() as $constantString) { + $types[] = self::getTypeLooselyEqualTo($constantString->getValue(), $phpVersion); } if ($this->emptyArray) { $types[] = TypeCombinator::union(new NullType(), new ConstantBooleanType(false), new ConstantArrayType([], [])); @@ -546,8 +516,8 @@ public function arithmeticPower(self $exponent, PhpVersion $phpVersion): Type private function powerMember(self $exponent, PhpVersion $phpVersion): Type { - foreach ($exponent->constantStrings as $constantString) { - if (!is_numeric($constantString)) { + foreach ($exponent->getConstantStrings() as $constantString) { + if (!is_numeric($constantString->getValue())) { return new ErrorType(); } } @@ -601,6 +571,86 @@ private function powerMember(self $exponent, PhpVersion $phpVersion): Type ]); } + /** Models the . operator: on the strings the values cast to. */ + public function stringConcatenate(self $right, PhpVersion $phpVersion): Type + { + $leftStrings = $this->toStringSet(); + $rightStrings = $right->toStringSet(); + if ($leftStrings === null || $rightStrings === null) { + return new ErrorType(); + } + if ($this->isEmpty() || $right->isEmpty()) { + return $this->never ?? $right->never ?? new NeverType(); + } + + return $leftStrings->concatenate($rightStrings, $phpVersion); + } + + /** The strings a (string) cast gives for the values, null when it is an error for one of them. */ + private function toStringSet(): ?StringSet + { + if ($this->invalid || $this->emptyArray || $this->nonEmptyArray) { + return null; + } + if ($this->unknown) { + return self::toStringSetOfType(($this->unknownType ?? new MixedType())->toString()); + } + + $strings = $this->strings ?? StringSet::createEmpty(); + if ($this->null || $this->false) { + $strings = $strings->union(StringSet::createConstant(new ConstantStringType(''))); + } + if ($this->true) { + $strings = $strings->union(StringSet::createConstant(new ConstantStringType('1'))); + } + if ($this->numbers !== null && !$this->numbers->isEmpty()) { + $numberStrings = self::toStringSetOfType(self::createTypeOfNumbers($this->numbers)->toString()); + if ($numberStrings === null) { + return null; + } + $strings = $strings->union($numberStrings->toNumberStrings(self::isOnlyNonNegativeIntegers($this->numbers))); + } + if ($this->objects !== null) { + $objectStrings = self::toStringSetOfType($this->objects->toString()); + if ($objectStrings === null) { + return null; + } + $strings = $strings->union($objectStrings); + } + if ($this->resources) { + $strings = $strings->union(StringSet::createType(new StringType())); + } + + return $strings; + } + + /** The strings of the type Type::toString() gives, null when it is an error. */ + private static function toStringSetOfType(Type $strings): ?StringSet + { + if ($strings instanceof ErrorType) { + return null; + } + + return $strings->toValueSet()->strings ?? StringSet::createEmpty(); + } + + private static function isOnlyNonNegativeIntegers(NumberSet $numbers): bool + { + foreach ($numbers->getIntegerIntervals() as [$min]) { + if ($min === null || $min < 0) { + return false; + } + } + + return $numbers->getFloatIntervals() === [] && !$numbers->hasNan(); + } + + /** @return list */ + private function getConstantStrings(): array + { + return $this->strings?->getConstantStrings() ?? []; + } + /** * The values one at a time, the way the members of a union type are: each kind of * value, each number interval, each constant string. The booleans and a float with @@ -631,11 +681,12 @@ private function getMembers(): array $members[] = self::createNumbers(new NumberSet([], [], true)); } } - foreach ($this->constantStrings as $constantString) { + foreach ($this->getConstantStrings() as $constantString) { $members[] = self::createConstantString($constantString); } - if ($this->strings !== null) { - $members[] = self::createStrings($this->strings); + $stringType = $this->strings?->getType(); + if ($stringType !== null) { + $members[] = self::createStrings($stringType); } return $members; @@ -655,15 +706,16 @@ private function toNumberSetOfNumber(): ?NumberSet if ($this->numbers !== null) { $numbers = $numbers->union($this->numbers); } - foreach ($this->constantStrings as $constantString) { - $stringNumbers = (new ConstantStringType($constantString))->toNumber()->toNumberSet(); + foreach ($this->getConstantStrings() as $constantString) { + $stringNumbers = $constantString->toNumber()->toNumberSet(); if ($stringNumbers === null) { return null; } $numbers = $numbers->union($stringNumbers); } - if ($this->strings !== null) { - $stringNumbers = $this->strings->toNumber()->toNumberSet(); + $stringType = $this->strings?->getType(); + if ($stringType !== null) { + $stringNumbers = $stringType->toNumber()->toNumberSet(); if ($stringNumbers === null) { return null; } @@ -803,7 +855,7 @@ private function generalizeTooManyConstants(self $right): array private function generalize(): self { $hasBooleans = $this->false || $this->true; - $hasStrings = $this->constantStrings !== []; + $hasStrings = $this->strings !== null; $hasNumbers = $this->numbers !== null && !$this->numbers->isEmpty(); if (!$hasBooleans && !$hasStrings && !$this->null && $hasNumbers) { if ($this->numbers->getFloatIntervals() === [] && !$this->numbers->hasNan()) { @@ -933,11 +985,8 @@ private function toNumberType(): ?Type $types[] = self::createTypeOfNumbers($this->numbers); } - $otherTypes = []; - foreach ($this->constantStrings as $constantString) { - $otherTypes[] = new ConstantStringType($constantString); - } - foreach ([$this->strings, $this->arrays, $this->objects] as $type) { + $otherTypes = $this->getConstantStrings(); + foreach ([$this->strings?->getType(), $this->arrays, $this->objects] as $type) { if ($type === null) { continue; } @@ -1236,7 +1285,7 @@ private function getFiniteScalarTypes(bool $expandIntegerIntervals): ?array { if ( $this->unknown - || $this->strings !== null + || $this->strings?->getType() !== null || $this->emptyArray || $this->nonEmptyArray || $this->objects !== null @@ -1288,8 +1337,8 @@ private function getFiniteScalarTypes(bool $expandIntegerIntervals): ?array $types[] = new ConstantFloatType(NAN); } } - foreach ($this->constantStrings as $constantString) { - $types[] = new ConstantStringType($constantString); + foreach ($this->getConstantStrings() as $constantString) { + $types[] = $constantString; } return $types; @@ -1322,12 +1371,10 @@ private function toOperandNumbers(bool $withoutArrays, bool $integers): ?NumberS $sets[] = $integers ? self::toIntegerNumbers($this->numbers) : $this->numbers; } - $types = []; - foreach ($this->constantStrings as $constantString) { - $types[] = new ConstantStringType($constantString); - } - if ($this->strings !== null) { - $types[] = $this->strings; + $types = $this->getConstantStrings(); + $stringType = $this->strings?->getType(); + if ($stringType !== null) { + $types[] = $stringType; } if ($this->objects !== null) { $types[] = $this->objects; @@ -1424,7 +1471,7 @@ private function isOnlyStrings(): bool private function mayBeString(): bool { - return $this->unknown || $this->constantStrings !== [] || $this->strings !== null; + return $this->unknown || $this->strings !== null; } private function mayBeArray(): bool @@ -1439,7 +1486,6 @@ private function mayBeNonArray(): bool || $this->false || $this->true || $this->numbers !== null - || $this->constantStrings !== [] || $this->strings !== null || $this->objects !== null || $this->resources; @@ -1451,7 +1497,6 @@ private function mayBeNonNumber(): bool || $this->null || $this->false || $this->true - || $this->constantStrings !== [] || $this->strings !== null || $this->mayBeArray() || $this->objects !== null diff --git a/tests/PHPStan/Analyser/nsrt/bcmath-number.php b/tests/PHPStan/Analyser/nsrt/bcmath-number.php index adcff1851fa..139151c258f 100644 --- a/tests/PHPStan/Analyser/nsrt/bcmath-number.php +++ b/tests/PHPStan/Analyser/nsrt/bcmath-number.php @@ -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', $a . $b); + assertType('*NEVER*', $a . $b); assertType('*ERROR*', $a ** $b); assertType('*NEVER*', $a << $b); assertType('*NEVER*', $a >> $b); diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 774ab8e5f26..0a03bf74317 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -569,18 +569,10 @@ public function testWritingReadonlyProperty(): void 'Property WritingToReadOnlyProperties\Foo::$usualProperty (int) does not accept string.', 24, ], - [ - 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty (int) does not accept string.', - 27, - ], [ 'Property WritingToReadOnlyProperties\Foo::$usualProperty (int) does not accept string.', 34, ], - [ - 'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty (int) does not accept string.', - 40, - ], ]); } diff --git a/turbo-ext/src/ConstantStringType.cpp b/turbo-ext/src/ConstantStringType.cpp index ed067e8881a..14bfcd82d84 100644 --- a/turbo-ext/src/ConstantStringType.cpp +++ b/turbo-ext/src/ConstantStringType.cpp @@ -1337,12 +1337,7 @@ PT_MINIT_REGISTRATION(pt_register_constant_string_type) reg::Class cls("PHPStan\\Type\\Constant\\ConstantStringType"); ptdecl::ConstantStringType::declareClass(cls); /* mirrors toValueSet() */ - cls.method(sigs::toValueSet, [](INTERNAL_FUNCTION_PARAMETERS) { - ZEND_PARSE_PARAMETERS_NONE(); - zv::Val value = pt_type_call(Z_OBJ_P(ZEND_THIS), PT_LC("getvalue"), 0, NULL); - if (UNEXPECTED(value.isUndef())) RETURN_THROWS(); - PT_RETURN_VAL(pt_value_set_create(PT_LC("createconstantstring"), 1, value.raw())); - }); + cls.method(sigs::toValueSet, PT_VALUE_SET_OF_THIS("createconstantstring")); cls.privateClassConstantLong("DESCRIBE_LIMIT", PT_CST_DESCRIBE_LIMIT); /* the declaration order defines the PT_CST_PROP_* slots */ cls.privateTypedClassPropertyDefaultNull("objectType", "PHPStan\\Type\\ObjectType"); diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp index 34cb376af8a..10f40077fb1 100644 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ b/turbo-ext/src/OperatorTypeHelper.cpp @@ -1,14 +1,12 @@ /* * PHPStanTurbo\OperatorTypeHelper — native implementation of - * PHPStan\Type\OperatorTypeHelper: the . operator (Type::stringConcatenate()) - * and the array + operator (Type::arrayUnion()) OperatorTypeTrait delegates - * to (pt_type_trait_operator() in TypeTraits.cpp calls - * pt_operator_type_helper_binary() directly). The arithmetic and bitwise - * operators are computed on ValueSet and NumberSet. + * PHPStan\Type\OperatorTypeHelper: the array + operator (Type::arrayUnion()) + * OperatorTypeTrait delegates to (pt_type_trait_operator() in TypeTraits.cpp + * calls pt_operator_type_helper_binary() directly). The other operators are + * computed on ValueSet, NumberSet and StringSet. * * A final class of static methods: every method mirrors its twin, the - * private ones are registered too so the method sets stay identical. Only - * concat() reads the PhpVersion (trailing whitespace in numeric strings). + * private ones are registered too so the method sets stay identical. */ #include "support.h" @@ -35,241 +33,6 @@ class OperatorTypeHelper { public: - /* {{{ concat() */ - - /* Mirrors concat(). */ - static zv::Val concat(zval *left, zval *right, zval *phpVersion) - { - 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)); - 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) { - /* '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; - - 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 ? isNumberAfterInteger(string) : 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(); - } - - /* 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; - } - - /* }}} */ - /* {{{ arrayUnion() */ /* Mirrors arrayUnion(). */ @@ -488,8 +251,6 @@ using phpstanturbo::OperatorTypeHelper; zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion) { switch (op) { - case PT_OPERATOR_CONCATENATE: - return OperatorTypeHelper::concat(left, right, phpVersion); case PT_OPERATOR_ARRAY_UNION: return OperatorTypeHelper::arrayUnion(left, right); } @@ -560,17 +321,8 @@ PT_MINIT_REGISTRATION(pt_register_operator_type_helper) ptdecl::OperatorTypeHelper::declareClass(cls); ptdecl::OperatorTypeHelper::declareProperties(cls); - cls.method(sigs::concat, OT_BINARY_GLUE(OperatorTypeHelper::concat(leftType, rightType, phpVersion))); cls.method(sigs::arrayUnion, OT_BINARY_GLUE(OperatorTypeHelper::arrayUnion(leftType, rightType))); cls.method(sigs::tryArrayUnion, OT_TYPES_GLUE(OperatorTypeHelper::tryArrayUnion(leftType, rightType))); - 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.shadow(&pt_ce_operator_type_helper); } diff --git a/turbo-ext/src/StringSet.cpp b/turbo-ext/src/StringSet.cpp new file mode 100644 index 00000000000..3c05f8e91e3 --- /dev/null +++ b/turbo-ext/src/StringSet.cpp @@ -0,0 +1,614 @@ +/* + * PHPStanTurbo\StringSet — native implementation of PHPStan\Type\StringSet. + * + * Declared as PHPStan\Type\StringSet itself at activation: final, no parent, + * no interfaces. State is the twin's `private array $constants`, `private + * ?Type $type`, `private bool $digits` and `private bool $whitespaceFree`, + * declared typed property slots, so the std object handlers do GC/clone. The + * twin's private constructor is not declared: the sets are made by the + * factories only, natively. + * + * The twin's private methods live only here, in StringSetOps, method for + * method. ValueSet.cpp reads the slots through the pt_string_set_*() entries. + */ + +#include "TypeTraits.h" +#include "IetrSupport.h" +#include "generated/StringSet.h" + +namespace slots = ptdecl::StringSet::slot; +namespace sigs = ptdecl::StringSet::sig; + +static zend_class_entry *pt_ce_string_set = nullptr; + +namespace phpstanturbo { + +using namespace phpstanturbo::ietr; + +/* a borrowed view of a StringSet's slots */ +class StringSetView +{ +public: + explicit StringSetView(zend_object *o) : o(o) {} + + HashTable *constants() const + { + zval *value = OBJ_PROP_NUM(o, slots::constants); + return Z_TYPE_P(value) == IS_ARRAY ? Z_ARRVAL_P(value) : (HashTable *) &zend_empty_array; + } + zval *type() const + { + zval *value = OBJ_PROP_NUM(o, slots::type); + return Z_TYPE_P(value) == IS_OBJECT ? value : nullptr; + } + bool digits() const { return Z_TYPE_P(OBJ_PROP_NUM(o, slots::digits)) == IS_TRUE; } + bool whitespaceFree() const { return Z_TYPE_P(OBJ_PROP_NUM(o, slots::whitespaceFree)) == IS_TRUE; } + bool isEmpty() const { return zend_hash_num_elements(constants()) == 0 && type() == nullptr; } + +private: + zend_object *o; +}; + +/* Mirrors the private methods of the twin; UNDEF / -1 = pending exception. */ +class StringSetOps +{ +public: + static constexpr const char *WHITESPACE = " \t\n\v\f\r"; + + /* new self($constants, $type, $digits, $whitespaceFree); type UNDEF or null for none */ + static zv::Val newStringSet(zv::Arr &constants, zv::Val type, bool digits, bool whitespaceFree) + { + zval object; + if (UNEXPECTED(object_init_ex(&object, pt_ce_string_set) != SUCCESS)) return zv::Val(); + zend_object *o = Z_OBJ(object); + zval *constantsSlot = OBJ_PROP_NUM(o, slots::constants); + zval *typeSlot = OBJ_PROP_NUM(o, slots::type); + zval *digitsSlot = OBJ_PROP_NUM(o, slots::digits); + zval *whitespaceFreeSlot = OBJ_PROP_NUM(o, slots::whitespaceFree); + zval_ptr_dtor(constantsSlot); + zval_ptr_dtor(typeSlot); + ZVAL_COPY(constantsSlot, constants.raw()); + if (type.isUndef() || Z_TYPE_P(type.raw()) == IS_NULL) { + ZVAL_NULL(typeSlot); + } else { + zval taken = type.take(); + ZVAL_COPY_VALUE(typeSlot, &taken); + } + ZVAL_BOOL(digitsSlot, digits); + ZVAL_BOOL(whitespaceFreeSlot, whitespaceFree); + Z_PROP_FLAG_P(constantsSlot) = 0; /* no longer IS_PROP_UNINIT */ + Z_PROP_FLAG_P(typeSlot) = 0; + Z_PROP_FLAG_P(digitsSlot) = 0; + Z_PROP_FLAG_P(whitespaceFreeSlot) = 0; + return zv::Val::adopt(object); + } + + static zv::Val createEmpty() + { + zv::Arr constants = zv::Arr::create(0); + return newStringSet(constants, zv::Val::null(), false, false); + } + + static zv::Val createConstant(zval *string) + { + zv::Arr constants = zv::Arr::create(1); + constants.push(zv::Ref(string)); + return newStringSet(constants, zv::Val::null(), false, false); + } + + static zv::Val createType(zval *strings) + { + IETR_TRI(decimal, triOn(strings, mIsDecimalIntegerString)); + zv::Arr constants = zv::Arr::create(0); + return newStringSet(constants, zv::Val::copyOf(zv::Ref(strings)), false, decimal == PT_TRI_YES); + } + + static zv::Val toNumberStrings(StringSetView s, bool digits) + { + zv::Arr constants = zv::Arr::copyOfTable(s.constants()); + return newStringSet(constants, copyOrNull(s.type()), digits, true); + } + + static zv::Val union_(StringSetView a, StringSetView b) + { + zv::Arr constants = zv::Arr::copyOfTable(a.constants()); + zval *entry; + ZEND_HASH_FOREACH_VAL(b.constants(), entry) { + IETR_VAL(value, constantStringValue(entry)); + int seen = contains(constants.table(), Z_STR_P(value.raw())); + if (UNEXPECTED(seen < 0)) return zv::Val(); + if (seen) continue; + constants.push(zv::Ref(entry)); + } ZEND_HASH_FOREACH_END(); + + if (a.type() == nullptr || b.type() == nullptr) { + StringSetView typed = a.type() == nullptr ? b : a; + return newStringSet(constants, copyOrNull(typed.type()), typed.digits(), typed.whitespaceFree()); + } + + IETR_VAL(type, union2(a.type(), b.type())); + return newStringSet(constants, std::move(type), a.digits() && b.digits(), a.whitespaceFree() && b.whitespaceFree()); + } + + static zv::Val intersect(StringSetView a, StringSetView b) + { + zv::Arr constants = zv::Arr::create(0); + StringSetView pairs[2][2] = {{a, b}, {b, a}}; + for (auto &pair : pairs) { + StringSetView set = pair[0]; + StringSetView otherSet = pair[1]; + zval *entry; + ZEND_HASH_FOREACH_VAL(set.constants(), entry) { + IETR_VAL(value, constantStringValue(entry)); + int seen = contains(constants.table(), Z_STR_P(value.raw())); + if (UNEXPECTED(seen < 0)) return zv::Val(); + if (seen) continue; + int inOther = contains(otherSet.constants(), Z_STR_P(value.raw())); + if (UNEXPECTED(inOther < 0)) return zv::Val(); + if (!inOther) { + if (otherSet.type() == nullptr) continue; + IETR_TRI(isSuperType, triOn(otherSet.type(), mIsSuperTypeOf, 1, entry)); + if (isSuperType == PT_TRI_NO) continue; + } + constants.push(zv::Ref(entry)); + } ZEND_HASH_FOREACH_END(); + } + + if (a.type() == nullptr || b.type() == nullptr) { + return newStringSet(constants, zv::Val::null(), false, false); + } + + zv::Args argv{a.type(), b.type()}; + IETR_VAL(type, pt_type_combinator_intersect(2, argv)); + return newStringSet(constants, std::move(type), a.digits() || b.digits(), a.whitespaceFree() || b.whitespaceFree()); + } + + static zv::Val toType(StringSetView s) + { + zv::Arr types = zv::Arr::copyOfTable(s.constants()); + if (s.type() != nullptr) types.push(zv::Ref(s.type())); + return unionOf(types); + } + + /* Mirrors concatenate(). */ + static zv::Val concatenate(StringSetView left, StringSetView right, zval *phpVersion) + { + if (left.isEmpty() || right.isEmpty()) return newNeverType(); + int leftEmptyString = isEmptyString(left); + if (UNEXPECTED(leftEmptyString < 0)) return zv::Val(); + if (leftEmptyString) return toType(right); + int rightEmptyString = isEmptyString(right); + if (UNEXPECTED(rightEmptyString < 0)) return zv::Val(); + if (rightEmptyString) return toType(left); + + // we limit the number of union-types for performance reasons + zend_long leftCount = zend_hash_num_elements(left.constants()); + zend_long rightCount = zend_hash_num_elements(right.constants()); + if (left.type() == nullptr && right.type() == nullptr && leftCount * rightCount <= PT_IETR_CALCULATE_SCALARS_LIMIT) { + zv::Arr strings = zv::Arr::create((uint32_t) (leftCount * rightCount)); + zval *leftConstant; + ZEND_HASH_FOREACH_VAL(left.constants(), leftConstant) { + int leftConstantEmpty = constantStringValueIsEmpty(leftConstant); + if (UNEXPECTED(leftConstantEmpty < 0)) return zv::Val(); + zval *rightConstant; + if (leftConstantEmpty) { + ZEND_HASH_FOREACH_VAL(right.constants(), rightConstant) { + strings.push(zv::Ref(rightConstant)); + } ZEND_HASH_FOREACH_END(); + continue; + } + + ZEND_HASH_FOREACH_VAL(right.constants(), rightConstant) { + int rightConstantEmpty = constantStringValueIsEmpty(rightConstant); + if (UNEXPECTED(rightConstantEmpty < 0)) return zv::Val(); + if (rightConstantEmpty) { + strings.push(zv::Ref(leftConstant)); + continue; + } + IETR_VAL(appended, callOn(leftConstant, mAppend, 1, rightConstant)); + strings.push(std::move(appended)); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + return unionOf(strings); + } + + zv::Arr accessoryTypes = zv::Arr::create(6); + { + IETR_TRI(leftNonEmpty, isNonEmpty(left)); + IETR_TRI(rightNonEmpty, isNonEmpty(right)); + int accessory = 0; /* 1 = non-falsy, 2 = non-empty */ + if (leftNonEmpty && rightNonEmpty) { + accessory = 1; + } else { + IETR_TRI(leftNonFalsy, isNonFalsy(left)); + IETR_TRI(rightNonFalsy, isNonFalsy(right)); + if (leftNonFalsy || rightNonFalsy) { + accessory = 1; + } else if (leftNonEmpty || rightNonEmpty) { + accessory = 2; + } + } + if (accessory != 0) { + zval type; + bool created = accessory == 1 ? pt_accessory_non_falsy_string_type_new(&type) : pt_accessory_non_empty_string_type_new(&type); + if (UNEXPECTED(!created)) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(type)); + } + } + + { + IETR_TRI(leftLiteral, isLiteral(left)); + IETR_TRI(rightLiteral, isLiteral(right)); + if (leftLiteral && rightLiteral) { + zval type; + if (UNEXPECTED(!pt_accessory_literal_string_type_new(&type))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(type)); + } + } + + { + IETR_TRI(leftLowercase, isLowercase(left)); + IETR_TRI(rightLowercase, isLowercase(right)); + if (leftLowercase && rightLowercase) { + zval type; + if (UNEXPECTED(!pt_accessory_lowercase_string_type_new(&type))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(type)); + } + } + + { + IETR_TRI(leftUppercase, isUppercase(left)); + IETR_TRI(rightUppercase, isUppercase(right)); + if (leftUppercase && rightUppercase) { + zval type; + if (UNEXPECTED(!pt_accessory_uppercase_string_type_new(&type))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(type)); + } + } + + { + IETR_TRI(numeric, isNumericOrEmpty(left, phpVersion)); + if (numeric) { + // '' . '' is not numeric + IETR_TRI(leftNonEmpty, isNonEmpty(left)); + IETR_TRI(rightNonEmpty, isNonEmpty(right)); + numeric = leftNonEmpty || rightNonEmpty; + } + if (numeric) { + // '1 ' is numeric on PHP 8, '1 5' is not + IETR_TRI(endsInWhitespace, canEndInWhitespace(left, phpVersion)); + numeric = !endsInWhitespace; + } + if (numeric) { + IETR_TRI(afterInteger, isDecimalInteger(left)); + IETR_TRI(suffix, isNumberSuffix(right, afterInteger, phpVersion)); + numeric = suffix; + } + if (numeric) { + zval type; + if (UNEXPECTED(!pt_accessory_numeric_string_type_new(&type))) return zv::Val(); + accessoryTypes.push(zv::Val::adopt(type)); + } + } + + if (zend_hash_num_elements(accessoryTypes.table()) > 0) { + IETR_VAL(stringType, newStringType()); + accessoryTypes.push(std::move(stringType)); + return newIntersectionType(accessoryTypes); + } + + return newStringType(); + } + + /* {{{ private methods: 1 / 0, -1 = pending exception */ + + static int isEmptyString(StringSetView s) + { + if (s.type() != nullptr || zend_hash_num_elements(s.constants()) != 1) return 0; + zval *constant = zend_hash_index_find(s.constants(), 0); + if (UNEXPECTED(constant == NULL)) return 0; + return constantStringValueIsEmpty(constant); + } + + /* the constants all pass `test` and the type answers yes to `query` */ + template + static int every(StringSetView s, const Method &query, Test test) + { + zval *constant; + ZEND_HASH_FOREACH_VAL(s.constants(), constant) { + zv::Val value = constantStringValue(constant); + if (UNEXPECTED(value.isUndef())) return -1; + if (!test(Z_STR_P(value.raw()))) return 0; + } ZEND_HASH_FOREACH_END(); + if (s.type() == nullptr) return 1; + zend_long answer = triOn(s.type(), query); + if (UNEXPECTED(answer < 0)) return -1; + return answer == PT_TRI_YES ? 1 : 0; + } + + static int isNonEmpty(StringSetView s) + { + return every(s, mIsNonEmptyString, [](zend_string *value) { return ZSTR_LEN(value) != 0; }); + } + + static int isNonFalsy(StringSetView s) + { + return every(s, mIsNonFalsyString, [](zend_string *value) { + return !(ZSTR_LEN(value) == 0 || (ZSTR_LEN(value) == 1 && ZSTR_VAL(value)[0] == '0')); + }); + } + + static int isLiteral(StringSetView s) + { + if (s.type() == nullptr) return 1; + zend_long answer = triOn(s.type(), mIsLiteralString); + if (UNEXPECTED(answer < 0)) return -1; + return answer == PT_TRI_YES ? 1 : 0; + } + + static int isLowercase(StringSetView s) + { + return every(s, mIsLowercaseString, [](zend_string *value) { + zend_string *lower = zend_string_tolower(value); + bool equal = zend_string_equals(lower, value); + zend_string_release(lower); + return equal; + }); + } + + static int isUppercase(StringSetView s) + { + return every(s, mIsUppercaseString, [](zend_string *value) { + zend_string *upper = zend_string_toupper(value); + bool equal = zend_string_equals(upper, value); + zend_string_release(upper); + return equal; + }); + } + + static int isDecimalInteger(StringSetView s) + { + return every(s, mIsDecimalIntegerString, [](zend_string *value) { + zval valueZv; + ZVAL_STR(&valueZv, value); + zend_string *roundTrip = zend_long_to_str(zval_get_long(&valueZv)); + bool equal = zend_string_equals(roundTrip, value); + zend_string_release(roundTrip); + return equal; + }); + } + + static int isNumericOrEmpty(StringSetView s, zval *phpVersion) + { + zval *constant; + ZEND_HASH_FOREACH_VAL(s.constants(), constant) { + zv::Val value = constantStringValue(constant); + if (UNEXPECTED(value.isUndef())) return -1; + zend_string *string = Z_STR_P(value.raw()); + if (ZSTR_LEN(string) == 0) continue; + int numeric = isNumeric(string, phpVersion); + if (numeric <= 0) return numeric; + } ZEND_HASH_FOREACH_END(); + if (s.type() == nullptr) return 1; + zend_long answer = triOn(s.type(), mIsNumericString); + if (UNEXPECTED(answer < 0)) return -1; + return answer == PT_TRI_YES ? 1 : 0; + } + + static int canEndInWhitespace(StringSetView s, zval *phpVersion) + { + zval *constant; + ZEND_HASH_FOREACH_VAL(s.constants(), constant) { + zv::Val value = constantStringValue(constant); + if (UNEXPECTED(value.isUndef())) return -1; + zend_string *string = Z_STR_P(value.raw()); + if (trimmedLength(string) != ZSTR_LEN(string)) return 1; + } ZEND_HASH_FOREACH_END(); + if (s.type() == nullptr || s.whitespaceFree()) 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; + } + + static int isNumberSuffix(StringSetView s, bool afterInteger, zval *phpVersion) + { + zval *constant; + ZEND_HASH_FOREACH_VAL(s.constants(), constant) { + zv::Val value = constantStringValue(constant); + if (UNEXPECTED(value.isUndef())) return -1; + zend_string *string = Z_STR_P(value.raw()); + if (ZSTR_LEN(string) == 0) continue; + int numeric = isNumeric(string, phpVersion); + if (UNEXPECTED(numeric < 0)) return -1; + if (!numeric || !(afterInteger ? isNumberAfterInteger(string) : isDecimalDigits(string))) return 0; + } ZEND_HASH_FOREACH_END(); + return s.type() == nullptr || s.digits() ? 1 : 0; + } + + /* rtrim($value, self::WHITESPACE)'s length */ + static size_t trimmedLength(zend_string *value) + { + size_t length = ZSTR_LEN(value); + while (length > 0 && strchr(WHITESPACE, ZSTR_VAL(value)[length - 1]) != NULL) length--; + return length; + } + + static int isNumeric(zend_string *value, zval *phpVersion) + { + size_t length = trimmedLength(value); + if (length != ZSTR_LEN(value)) { + bool allows; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS, allows))) return -1; + if (!allows) return 0; + } + return is_numeric_string(ZSTR_VAL(value), length, NULL, NULL, false) != 0 ? 1 : 0; + } + + static int contains(HashTable *constants, zend_string *value) + { + zval *constant; + ZEND_HASH_FOREACH_VAL(constants, constant) { + zv::Val constantValue = constantStringValue(constant); + if (UNEXPECTED(constantValue.isUndef())) return -1; + if (zend_string_equals(Z_STR_P(constantValue.raw()), value)) return 1; + } ZEND_HASH_FOREACH_END(); + return 0; + } + + /* }}} */ + +private: + static zv::Val copyOrNull(zval *value) + { + return value == nullptr ? zv::Val::null() : zv::Val::copyOf(zv::Ref(value)); + } +}; + +} // namespace phpstanturbo + +using phpstanturbo::StringSetOps; +using phpstanturbo::StringSetView; + +/* {{{ native entries */ + +zend_class_entry *pt_string_set_class() +{ + return pt_ce_string_set; +} + +zv::Val pt_string_set_create_empty() +{ + return StringSetOps::createEmpty(); +} + +zv::Val pt_string_set_create_constant(zval *string) +{ + return StringSetOps::createConstant(string); +} + +zv::Val pt_string_set_create_type(zval *strings) +{ + return StringSetOps::createType(strings); +} + +zv::Val pt_string_set_to_number_strings(zval *set, bool digits) +{ + return StringSetOps::toNumberStrings(StringSetView(Z_OBJ_P(set)), digits); +} + +zv::Val pt_string_set_union(zval *a, zval *b) +{ + return StringSetOps::union_(StringSetView(Z_OBJ_P(a)), StringSetView(Z_OBJ_P(b))); +} + +zv::Val pt_string_set_intersect(zval *a, zval *b) +{ + return StringSetOps::intersect(StringSetView(Z_OBJ_P(a)), StringSetView(Z_OBJ_P(b))); +} + +zv::Val pt_string_set_concatenate(zval *left, zval *right, zval *phpVersion) +{ + return StringSetOps::concatenate(StringSetView(Z_OBJ_P(left)), StringSetView(Z_OBJ_P(right)), phpVersion); +} + +HashTable *pt_string_set_constants(zval *set) +{ + return StringSetView(Z_OBJ_P(set)).constants(); +} + +zval *pt_string_set_type(zval *set) +{ + return StringSetView(Z_OBJ_P(set)).type(); +} + +bool pt_string_set_is_empty(zval *set) +{ + return StringSetView(Z_OBJ_P(set)).isEmpty(); +} + +/* }}} */ + +/* {{{ engine ABI glue: parameter parsing + registration */ + +#define PT_SELF StringSetView(Z_OBJ_P(ZEND_THIS)) + +/* an argument of the twin's `self` / class-typed parameter: the TypeError + * of its strict-types call for any other value */ +static bool requireInstance(zval *value, zend_class_entry *ce, uint32_t position, const char *name) +{ + if (EXPECTED(ce != NULL && Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), ce))) return true; + zend_type_error("%s::%s(): Argument #%u ($%s) must be of type %s, %s given", ZSTR_VAL(pt_ce_string_set->name), get_active_function_name(), position, name, ce != NULL ? ZSTR_VAL(ce->name) : "?", zend_zval_value_name(value)); + return false; +} + +template +static void pt_ss_combine(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *other; + if (!zp::parse(execute_data, other)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(other, pt_ce_string_set, 1, "other"))) RETURN_THROWS(); + PT_RETURN_VAL(Combine(PT_SELF, StringSetView(Z_OBJ_P(other)))); +} + +PT_MINIT_REGISTRATION(pt_register_string_set) +{ + reg::Class cls("PHPStan\\Type\\StringSet"); + ptdecl::StringSet::declareClass(cls); + cls.privateClassConstantString("WHITESPACE", StringSetOps::WHITESPACE); + /* "constants", "type", "digits" and "whitespaceFree" must stay the + * declared properties in this order (slots::*) */ + ptdecl::StringSet::declareProperties(cls); + + cls.method(sigs::createEmpty, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(StringSetOps::createEmpty()); + }); + cls.method(sigs::createConstant, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *string; + if (!zp::parse(execute_data, string)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(string, pt_ce_constant_string_type, 1, "string"))) RETURN_THROWS(); + PT_RETURN_VAL(StringSetOps::createConstant(string)); + }); + cls.method(sigs::createType, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *strings; + if (!zp::parse(execute_data, strings)) RETURN_THROWS(); + PT_RETURN_VAL(StringSetOps::createType(strings)); + }); + cls.method(sigs::toNumberStrings, [](INTERNAL_FUNCTION_PARAMETERS) { + bool digits; + if (!zp::parse(execute_data, digits)) RETURN_THROWS(); + PT_RETURN_VAL(StringSetOps::toNumberStrings(PT_SELF, digits)); + }); + cls.method(sigs::getConstantStrings, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_ARR(zend_array_dup(PT_SELF.constants())); + }); + cls.method(sigs::getType, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + zval *type = PT_SELF.type(); + if (type == nullptr) RETURN_NULL(); + RETURN_COPY(type); + }); + cls.method(sigs::isEmpty, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_BOOL(PT_SELF.isEmpty()); + }); + cls.method(sigs::union_, pt_ss_combine); + cls.method(sigs::intersect, pt_ss_combine); + cls.method(sigs::toType, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(StringSetOps::toType(PT_SELF)); + }); + cls.method(sigs::concatenate, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(right, pt_ce_string_set, 1, "right"))) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(phpVersion, pt_class(PT_CLASS_PHP_VERSION), 2, "phpVersion"))) RETURN_THROWS(); + PT_RETURN_VAL(StringSetOps::concatenate(PT_SELF, StringSetView(Z_OBJ_P(right)), phpVersion)); + }); + + cls.shadow(&pt_ce_string_set); +} + +/* }}} */ diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index dc21af59043..9ad77aef64a 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -5620,7 +5620,7 @@ void pt_type_trait_operator(reg::Class &cls) cls.traitMethod(sigs::bitwiseXor, PT_OPERATOR_VALUE_SET("bitwisexor")); cls.traitMethod(sigs::bitwiseShiftLeft, PT_OPERATOR_VALUE_SET("bitwiseshiftleft")); cls.traitMethod(sigs::bitwiseShiftRight, PT_OPERATOR_VALUE_SET("bitwiseshiftright")); - cls.traitMethod(sigs::stringConcatenate, operatorBinary); + cls.traitMethod(sigs::stringConcatenate, PT_OPERATOR_VALUE_SET("stringconcatenate")); cls.traitMethod(sigs::arrayUnion, operatorBinary); /* $this->toValueSet()->getPossiblyLooselyEqualType($phpVersion) */ cls.traitMethod(sigs::getPossiblyLooselyEqualType, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 15b845fb991..5e84c498cf9 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -349,6 +349,21 @@ zv::Val pt_number_set_new(const phpstanturbo::NumberSetData &data); * pending exception */ [[nodiscard]] bool pt_type_to_number_set(zval *type, phpstanturbo::NumberSetData &out, bool &has); +/* the StringSet class entry and its twin's methods (StringSet.cpp); the + * zvals are borrowed StringSet instances, UNDEF = pending exception */ +zend_class_entry *pt_string_set_class(); +zv::Val pt_string_set_create_empty(); +zv::Val pt_string_set_create_constant(zval *string); +zv::Val pt_string_set_create_type(zval *strings); +zv::Val pt_string_set_to_number_strings(zval *set, bool digits); +zv::Val pt_string_set_union(zval *a, zval *b); +zv::Val pt_string_set_intersect(zval *a, zval *b); +zv::Val pt_string_set_concatenate(zval *left, zval *right, zval *phpVersion); +/* the set's slots, borrowed: its ConstantStringType list, its type or nullptr */ +HashTable *pt_string_set_constants(zval *set); +zval *pt_string_set_type(zval *set); +bool pt_string_set_is_empty(zval *set); + /* the bodies of IntegerType::looseCompare() and IntegerType::arithmeticPower() * (IntegerType.cpp), for the children's parent:: calls; UNDEF = pending * exception */ diff --git a/turbo-ext/src/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp index 2141ffceee1..f18a58c64f8 100644 --- a/turbo-ext/src/ValueSet.cpp +++ b/turbo-ext/src/ValueSet.cpp @@ -2,7 +2,7 @@ * PHPStanTurbo\ValueSet — native implementation of PHPStan\Type\ValueSet. * * Declared as PHPStan\Type\ValueSet itself at activation: final, no parent, - * no interfaces. State is the twin's sixteen private typed properties, as + * no interfaces. State is the twin's fifteen private typed properties, as * declared slots, so the std object handlers do GC/clone. The twin's * private constructor is not declared: the sets are made by the factories * only, natively; an uninitialized slot reads as the twin's default. @@ -45,7 +45,6 @@ struct ValueSetFields bool false_ = false; bool true_ = false; zv::Val numbers; - zv::Val constantStrings; zv::Val strings; bool emptyArray = false; bool nonEmptyArray = false; @@ -93,13 +92,20 @@ class ValueSetView bool false_() const { return flag(slots::false_); } bool true_() const { return flag(slots::true_); } zval *numbers() const { return objectOrNull(slots::numbers); } + /* the StringSet, or nullptr */ + zval *strings() const { return objectOrNull(slots::strings); } + /* $this->strings?->getConstantStrings() ?? [], borrowed ConstantStringType objects */ HashTable *constantStrings() const { - zval *value = OBJ_PROP_NUM(o, slots::constantStrings); - return Z_TYPE_P(value) == IS_ARRAY ? Z_ARRVAL_P(value) : (HashTable *) &zend_empty_array; + zval *set = strings(); + return set == nullptr ? (HashTable *) &zend_empty_array : pt_string_set_constants(set); + } + /* $this->strings?->getType() */ + zval *stringType() const + { + zval *set = strings(); + return set == nullptr ? nullptr : pt_string_set_type(set); } - bool hasConstantStrings() const { return zend_hash_num_elements(constantStrings()) > 0; } - zval *strings() const { return objectOrNull(slots::strings); } bool emptyArray() const { return flag(slots::emptyArray); } bool nonEmptyArray() const { return flag(slots::nonEmptyArray); } zval *arrays() const { return objectOrNull(slots::arrays); } @@ -157,7 +163,6 @@ static zv::Val newValueSet(ValueSetFields &fields) writeFlag(o, slots::false_, fields.false_); writeFlag(o, slots::true_, fields.true_); writeValue(o, slots::numbers, fields.numbers, false); - writeValue(o, slots::constantStrings, fields.constantStrings, true); writeValue(o, slots::strings, fields.strings, false); writeFlag(o, slots::emptyArray, fields.emptyArray); writeFlag(o, slots::nonEmptyArray, fields.nonEmptyArray); @@ -267,19 +272,19 @@ class ValueSetOps return createNumbers(std::move(numbers)); } - static zv::Val createConstantString(zend_string *value) + static zv::Val createConstantString(zval *string) { ValueSetFields f; - zv::Arr constantStrings = zv::Arr::create(1); - constantStrings.push(zv::Val::string(value)); - f.constantStrings = std::move(constantStrings); + f.strings = pt_string_set_create_constant(string); + if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); return newValueSet(f); } static zv::Val createStrings(zval *strings) { ValueSetFields f; - f.strings = zv::Val::copyOf(zv::Ref(strings)); + f.strings = pt_string_set_create_type(strings); + if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); return newValueSet(f); } @@ -337,7 +342,6 @@ class ValueSetOps && !s.false_() && !s.true_() && numbersEmpty(s.numbers()) - && !s.hasConstantStrings() && s.strings() == nullptr && !s.emptyArray() && !s.nonEmptyArray() @@ -357,7 +361,7 @@ class ValueSetOps static bool mayBeString(ValueSetView s) { - return s.unknown() || s.hasConstantStrings() || s.strings() != nullptr; + return s.unknown() || s.strings() != nullptr; } static bool mayBeArray(ValueSetView s) @@ -372,7 +376,6 @@ class ValueSetOps || s.false_() || s.true_() || s.numbers() != nullptr - || s.hasConstantStrings() || s.strings() != nullptr || s.objects() != nullptr || s.resources(); @@ -384,7 +387,6 @@ class ValueSetOps || s.null_() || s.false_() || s.true_() - || s.hasConstantStrings() || s.strings() != nullptr || mayBeArray(s) || s.objects() != nullptr @@ -444,15 +446,6 @@ class ValueSetOps return union2(left, right); } - static bool containsString(HashTable *strings, zend_string *value) - { - zval *entry; - ZEND_HASH_FOREACH_VAL(strings, entry) { - if (Z_TYPE_P(entry) == IS_STRING && zend_string_equals(Z_STR_P(entry), value)) return true; - } ZEND_HASH_FOREACH_END(); - return false; - } - static zv::Val union_(ValueSetView a, ValueSetView b) { if (a.unknown() || b.unknown()) { @@ -467,13 +460,6 @@ class ValueSetOps return newValueSet(f); } - zv::Arr constantStrings = zv::Arr::copyOfTable(a.constantStrings()); - zval *entry; - ZEND_HASH_FOREACH_VAL(b.constantStrings(), entry) { - if (containsString(constantStrings.table(), Z_STR_P(entry))) continue; - constantStrings.push(zv::Ref(entry)); - } ZEND_HASH_FOREACH_END(); - ValueSetFields f; f.null_ = a.null_() || b.null_(); f.false_ = a.false_() || b.false_(); @@ -484,9 +470,12 @@ class ValueSetOps f.numbers = callOn(a.numbers(), mUnion, 1, b.numbers()); if (UNEXPECTED(f.numbers.isUndef())) return zv::Val(); } - f.constantStrings = std::move(constantStrings); - f.strings = unionTypes(a.strings(), b.strings()); - if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); + if (a.strings() == nullptr || b.strings() == nullptr) { + f.strings = copyOrNull(a.strings() != nullptr ? a.strings() : b.strings()); + } else { + f.strings = pt_string_set_union(a.strings(), b.strings()); + if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); + } f.emptyArray = a.emptyArray() || b.emptyArray(); f.nonEmptyArray = a.nonEmptyArray() || b.nonEmptyArray(); f.arrays = unionTypes(a.arrays(), b.arrays()); @@ -511,26 +500,6 @@ class ValueSetOps if (a.unknown()) return objectValue(b); if (b.unknown()) return objectValue(a); - zv::Arr constantStrings = zv::Arr::create(0); - ValueSetView pairs[2][2] = {{a, b}, {b, a}}; - for (auto &pair : pairs) { - ValueSetView set = pair[0]; - ValueSetView otherSet = pair[1]; - zval *entry; - ZEND_HASH_FOREACH_VAL(set.constantStrings(), entry) { - zend_string *constantString = Z_STR_P(entry); - if (containsString(constantStrings.table(), constantString)) continue; - if (!containsString(otherSet.constantStrings(), constantString)) { - if (otherSet.strings() == nullptr) continue; - IETR_VAL(constantType, newConstantStringType(constantString)); - zend_long isSuperType = triOn(otherSet.strings(), mIsSuperTypeOf, 1, constantType.raw()); - if (UNEXPECTED(isSuperType < 0)) return zv::Val(); - if (isSuperType == PT_TRI_NO) continue; - } - constantStrings.push(zv::Ref(entry)); - } ZEND_HASH_FOREACH_END(); - } - ValueSetFields f; f.null_ = a.null_() && b.null_(); f.false_ = a.false_() && b.false_(); @@ -539,9 +508,10 @@ class ValueSetOps f.numbers = callOn(a.numbers(), mIntersect, 1, b.numbers()); if (UNEXPECTED(f.numbers.isUndef())) return zv::Val(); } - f.constantStrings = std::move(constantStrings); - f.strings = intersectTypes(a.strings(), b.strings()); - if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); + if (a.strings() != nullptr && b.strings() != nullptr) { + IETR_VAL(strings, pt_string_set_intersect(a.strings(), b.strings())); + if (!pt_string_set_is_empty(strings.raw())) f.strings = std::move(strings); + } f.emptyArray = a.emptyArray() && b.emptyArray(); f.nonEmptyArray = a.nonEmptyArray() && b.nonEmptyArray(); f.arrays = intersectTypes(a.arrays(), b.arrays()); @@ -752,15 +722,13 @@ class ValueSetOps if (s.numbers() != nullptr && UNEXPECTED(!readNumbers(s.numbers(), out))) return false; zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - zv::Val constantType = newConstantStringType(Z_STR_P(entry)); - if (UNEXPECTED(constantType.isUndef())) return false; bool hasNumbers; - if (UNEXPECTED(!convertedNumbers(constantType.raw(), mToNumber, out, hasNumbers))) return false; + if (UNEXPECTED(!convertedNumbers(entry, mToNumber, out, hasNumbers))) return false; if (!hasNumbers) return true; } ZEND_HASH_FOREACH_END(); - if (s.strings() != nullptr) { + if (s.stringType() != nullptr) { bool hasNumbers; - if (UNEXPECTED(!convertedNumbers(s.strings(), mToNumber, out, hasNumbers))) return false; + if (UNEXPECTED(!convertedNumbers(s.stringType(), mToNumber, out, hasNumbers))) return false; if (!hasNumbers) return true; } has = true; @@ -835,11 +803,9 @@ class ValueSetOps std::vector types; zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - zv::Val constantType = newConstantStringType(Z_STR_P(entry)); - if (UNEXPECTED(constantType.isUndef())) return false; - types.push_back(std::move(constantType)); + types.push_back(zv::Val::copyOf(zv::Ref(entry))); } ZEND_HASH_FOREACH_END(); - if (s.strings() != nullptr) types.push_back(zv::Val::copyOf(zv::Ref(s.strings()))); + if (s.stringType() != nullptr) types.push_back(zv::Val::copyOf(zv::Ref(s.stringType()))); if (s.objects() != nullptr) types.push_back(zv::Val::copyOf(zv::Ref(s.objects()))); for (zv::Val &type : types) { NumberSetData numbers; @@ -934,7 +900,7 @@ class ValueSetOps static bool getFiniteScalarTypes(ValueSetView s, bool expandIntegerIntervals, std::vector &types, bool &finite) { finite = false; - if (s.unknown() || s.strings() != nullptr || s.emptyArray() || s.nonEmptyArray() || s.objects() != nullptr || s.resources()) return true; + if (s.unknown() || s.stringType() != nullptr || s.emptyArray() || s.nonEmptyArray() || s.objects() != nullptr || s.resources()) return true; if (s.null_()) { zv::Val nullType = newNullType(); @@ -984,9 +950,7 @@ class ValueSetOps } zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - zv::Val constantType = newConstantStringType(Z_STR_P(entry)); - if (UNEXPECTED(constantType.isUndef())) return false; - types.push_back(std::move(constantType)); + types.push_back(zv::Val::copyOf(zv::Ref(entry))); } ZEND_HASH_FOREACH_END(); finite = true; @@ -1204,7 +1168,7 @@ class ValueSetOps static zv::Val generalize(ValueSetView s) { bool hasBooleans = s.false_() || s.true_(); - bool hasStrings = s.hasConstantStrings(); + bool hasStrings = s.strings() != nullptr; bool hasNumbers = !numbersEmpty(s.numbers()); if (!hasBooleans && !hasStrings && !s.null_() && hasNumbers) { NumbersView n(s.numbers()); @@ -1285,10 +1249,9 @@ class ValueSetOps std::vector otherTypes; zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - IETR_VAL(constantType, newConstantStringType(Z_STR_P(entry))); - otherTypes.push_back(std::move(constantType)); + otherTypes.push_back(zv::Val::copyOf(zv::Ref(entry))); } ZEND_HASH_FOREACH_END(); - for (zval *type : {s.strings(), s.arrays(), s.objects()}) { + for (zval *type : {s.stringType(), s.arrays(), s.objects()}) { if (type == nullptr) continue; otherTypes.push_back(zv::Val::copyOf(zv::Ref(type))); } @@ -1537,12 +1500,12 @@ class ValueSetOps } zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - zv::Val member = createConstantString(Z_STR_P(entry)); + zv::Val member = createConstantString(entry); if (UNEXPECTED(member.isUndef())) return false; members.push_back(std::move(member)); } ZEND_HASH_FOREACH_END(); - if (s.strings() != nullptr) { - zv::Val member = createStrings(s.strings()); + if (s.stringType() != nullptr) { + zv::Val member = createStrings(s.stringType()); if (UNEXPECTED(member.isUndef())) return false; members.push_back(std::move(member)); } @@ -1553,7 +1516,8 @@ class ValueSetOps { zval *entry; ZEND_HASH_FOREACH_VAL(exponent.constantStrings(), entry) { - if (!isNumeric(Z_STR_P(entry))) return newErrorType(); + IETR_VAL(value, constantStringValue(entry)); + if (!isNumeric(Z_STR_P(value.raw()))) return newErrorType(); } ZEND_HASH_FOREACH_END(); std::vector baseTypes; @@ -1633,6 +1597,99 @@ class ValueSetOps return unionOf(resultTypes); } + /* {{{ stringConcatenate() */ + + /* Mirrors stringConcatenate(). */ + static zv::Val stringConcatenate(ValueSetView left, ValueSetView right, zval *phpVersion) + { + IETR_VAL(leftStrings, toStringSet(left)); + IETR_VAL(rightStrings, toStringSet(right)); + if (Z_TYPE_P(leftStrings.raw()) == IS_NULL || Z_TYPE_P(rightStrings.raw()) == IS_NULL) return newErrorType(); + if (isEmpty(left) || isEmpty(right)) { + zval *never = left.never() != nullptr ? left.never() : right.never(); + if (never != nullptr) return zv::Val::copyOf(zv::Ref(never)); + return newNeverType(); + } + + return pt_string_set_concatenate(leftStrings.raw(), rightStrings.raw(), phpVersion); + } + + /* Mirrors toStringSet(): a StringSet, or PHP null */ + static zv::Val toStringSet(ValueSetView s) + { + if (s.invalid() || s.emptyArray() || s.nonEmptyArray()) return zv::Val::null(); + if (s.unknown()) { + zv::Val mixed; + zval *unknownType = s.unknownType(); + if (unknownType == nullptr) { + mixed = newMixedType(); + if (UNEXPECTED(mixed.isUndef())) return zv::Val(); + unknownType = mixed.raw(); + } + IETR_VAL(strings, callOn(unknownType, mToString)); + return toStringSetOfType(strings.raw()); + } + + zv::Val strings = s.strings() != nullptr ? zv::Val::copyOf(zv::Ref(s.strings())) : pt_string_set_create_empty(); + if (UNEXPECTED(strings.isUndef())) return zv::Val(); + auto unionWith = [&strings](zv::Val other) -> bool { + if (UNEXPECTED(other.isUndef())) return false; + zv::Val united = pt_string_set_union(strings.raw(), other.raw()); + if (UNEXPECTED(united.isUndef())) return false; + strings = std::move(united); + return true; + }; + if (s.null_() || s.false_()) { + IETR_VAL(empty, constantStringLiteral("", 0)); + if (UNEXPECTED(!unionWith(pt_string_set_create_constant(empty.raw())))) return zv::Val(); + } + if (s.true_()) { + IETR_VAL(one, constantStringLiteral("1", 1)); + if (UNEXPECTED(!unionWith(pt_string_set_create_constant(one.raw())))) return zv::Val(); + } + if (!numbersEmpty(s.numbers())) { + NumberSetData numbers; + if (UNEXPECTED(!readNumbers(s.numbers(), numbers))) return zv::Val(); + IETR_VAL(numbersType, createTypeOfNumbers(numbers)); + IETR_VAL(numberStringType, callOn(numbersType.raw(), mToString)); + IETR_VAL(numberStrings, toStringSetOfType(numberStringType.raw())); + if (Z_TYPE_P(numberStrings.raw()) == IS_NULL) return numberStrings; + if (UNEXPECTED(!unionWith(pt_string_set_to_number_strings(numberStrings.raw(), isOnlyNonNegativeIntegers(numbers))))) return zv::Val(); + } + if (s.objects() != nullptr) { + IETR_VAL(objectStringType, callOn(s.objects(), mToString)); + IETR_VAL(objectStrings, toStringSetOfType(objectStringType.raw())); + if (Z_TYPE_P(objectStrings.raw()) == IS_NULL) return objectStrings; + if (UNEXPECTED(!unionWith(std::move(objectStrings)))) return zv::Val(); + } + if (s.resources()) { + IETR_VAL(stringType, newStringType()); + if (UNEXPECTED(!unionWith(pt_string_set_create_type(stringType.raw())))) return zv::Val(); + } + + return strings; + } + + /* Mirrors toStringSetOfType(): a StringSet, or PHP null */ + static zv::Val toStringSetOfType(zval *strings) + { + if (isA(strings, pt_ce_error_type)) return zv::Val::null(); + IETR_VAL(valueSet, pt_type_call(Z_OBJ_P(strings), PT_LC("tovalueset"), 0, NULL)); + zval *set = ValueSetView(Z_OBJ_P(valueSet.raw())).strings(); + if (set == nullptr) return pt_string_set_create_empty(); + return zv::Val::copyOf(zv::Ref(set)); + } + + static bool isOnlyNonNegativeIntegers(const NumberSetData &numbers) + { + for (const NumberSetData::IntegerInterval &interval : numbers.integerIntervals) { + if (interval.min.isNull || interval.min.value < 0) return false; + } + return numbers.floatIntervals.empty() && !numbers.nan; + } + + /* }}} */ + static zv::Val arithmeticNegate(ValueSetView s, zval *phpVersion) { if (isEmpty(s)) { @@ -2126,11 +2183,12 @@ class ValueSetOps } zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - IETR_VAL(partner, getConstantStringPartnerType(entry, phpVersion)); + IETR_VAL(value, constantStringValue(entry)); + IETR_VAL(partner, getConstantStringPartnerType(value.raw(), phpVersion)); types.push(std::move(partner)); } ZEND_HASH_FOREACH_END(); - if (s.strings() != nullptr) { - IETR_VAL(partner, getStringPartnerType(s.strings(), phpVersion)); + if (s.stringType() != nullptr) { + IETR_VAL(partner, getStringPartnerType(s.stringType(), phpVersion)); types.push(std::move(partner)); } if (s.emptyArray() || s.nonEmptyArray()) { @@ -2153,7 +2211,7 @@ class ValueSetOps static zv::Val getAlwaysLooselyEqualType(ValueSetView s, zval *phpVersion) { - if (s.unknown() || s.strings() != nullptr || s.nonEmptyArray() || s.resources()) return newNeverType(); + if (s.unknown() || s.stringType() != nullptr || s.nonEmptyArray() || s.resources()) return newNeverType(); std::vector types; if (s.null_()) { @@ -2204,7 +2262,8 @@ class ValueSetOps } zval *entry; ZEND_HASH_FOREACH_VAL(s.constantStrings(), entry) { - IETR_VAL(type, getTypeLooselyEqualTo(entry, phpVersion)); + IETR_VAL(value, constantStringValue(entry)); + IETR_VAL(type, getTypeLooselyEqualTo(value.raw(), phpVersion)); types.push_back(std::move(type)); } ZEND_HASH_FOREACH_END(); if (s.emptyArray()) { @@ -2319,7 +2378,7 @@ PT_MINIT_REGISTRATION(pt_register_value_set) cls.privateClassConstantLong("EXACT_FLOAT_INTEGER_LIMIT", ValueSetOps::EXACT_FLOAT_INTEGER_LIMIT); cls.privateClassConstantLong("EXACT_FLOAT_DIGITS", ValueSetOps::EXACT_FLOAT_DIGITS); cls.privateClassConstantLong("SHORT_FLOAT_DIGITS", ValueSetOps::SHORT_FLOAT_DIGITS); - /* the sixteen properties must stay the declared ones in this order (slots::*) */ + /* the fifteen properties must stay the declared ones in this order (slots::*) */ ptdecl::ValueSet::declareProperties(cls); cls.method(sigs::createUnknown, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -2369,9 +2428,10 @@ PT_MINIT_REGISTRATION(pt_register_value_set) PT_RETURN_VAL(ValueSetOps::createNumbers(zv::Val::copyOf(zv::Ref(numbers)))); }); cls.method(sigs::createConstantString, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_string *value; - if (!zp::parse(execute_data, value)) RETURN_THROWS(); - PT_RETURN_VAL(ValueSetOps::createConstantString(value)); + zval *string; + if (!zp::parse(execute_data, string)) RETURN_THROWS(); + if (UNEXPECTED(!requireInstance(string, pt_ce_constant_string_type, 1, "string"))) RETURN_THROWS(); + PT_RETURN_VAL(ValueSetOps::createConstantString(string)); }); cls.method(sigs::createStrings, [](INTERNAL_FUNCTION_PARAMETERS) { zval *strings; @@ -2444,6 +2504,7 @@ PT_MINIT_REGISTRATION(pt_register_value_set) cls.method(sigs::bitwiseXor, pt_vs_operator>); cls.method(sigs::bitwiseShiftLeft, pt_vs_operator>); cls.method(sigs::bitwiseShiftRight, pt_vs_operator>); + cls.method(sigs::stringConcatenate, pt_vs_operator); cls.shadow(&pt_ce_value_set); } diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h index 87d06ec2e44..2f0cb94e7ba 100644 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ b/turbo-ext/src/generated/OperatorTypeHelper.h @@ -22,45 +22,30 @@ inline void declareProperties(reg::Class &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 */ - "leftStringType\0" /* 71 */ - "canEndInWhitespace\0" /* 86 */ - "leftType\0" /* 105 */ - "rightType\0" /* 114 */ - "arrayUnion\0" /* 124 */ - "tryArrayUnion"; /* 135 */ + "leftType\0" /* 0 */ + "PHPStan\\Type\\Type\0" /* 9 */ + "rightType\0" /* 27 */ + "phpVersion\0" /* 37 */ + "PHPStan\\Php\\PhpVersion\0" /* 48 */ + "\0" /* 71 */ + "arrayUnion\0" /* 72 */ + "tryArrayUnion"; /* 83 */ 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(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), /* 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(105, 0, 5), /* tryArrayUnion $leftType */ - reg::packed(114, 0, 5), /* tryArrayUnion $rightType */ - reg::packed(63, MAY_BE_NULL, 5), /* tryArrayUnion return */ + reg::packed(0, 0, 9), /* arrayUnion $leftType */ + reg::packed(27, 0, 9), /* arrayUnion $rightType */ + reg::packed(37, 0, 48), /* arrayUnion $phpVersion */ + reg::packed(71, 0, 9), /* arrayUnion return */ + reg::packed(0, 0, 9), /* tryArrayUnion $leftType */ + reg::packed(27, 0, 9), /* tryArrayUnion $rightType */ + reg::packed(71, MAY_BE_NULL, 9), /* tryArrayUnion 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 canEndInWhitespace = { { 86 /* canEndInWhitespace */, 3, 4, 3, 7, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 124 /* arrayUnion */, 3, 8, 3, 11, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 135 /* tryArrayUnion */, 2, 12, 2, 14, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arrayUnion = { { 72 /* arrayUnion */, 3, 0, 3, 3, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig tryArrayUnion = { { 83 /* tryArrayUnion */, 2, 4, 2, 6, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::OperatorTypeHelper diff --git a/turbo-ext/src/generated/StringSet.h b/turbo-ext/src/generated/StringSet.h new file mode 100644 index 00000000000..6932d7461a1 --- /dev/null +++ b/turbo-ext/src/generated/StringSet.h @@ -0,0 +1,153 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Type/StringSet.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_STRING_SET_H +#define PHPSTANTURBO_GENERATED_STRING_SET_H + +#include "../reg.h" + +namespace ptdecl::StringSet { + +/* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ +namespace slot { +inline constexpr uint32_t constants = 0; +inline constexpr uint32_t type = 1; +inline constexpr uint32_t digits = 2; +inline constexpr uint32_t whitespaceFree = 3; +} // namespace slot + +inline void declareClass(reg::Class &cls) +{ + cls.final(); +} + +/* the properties the class declares itself, in declaration order (a used trait's come from its registrar) */ +inline void declareProperties(reg::Class &cls) +{ + cls.property("constants", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); + cls.property("type", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); + cls.property("digits", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("whitespaceFree", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "constants\0" /* 0 */ + "type\0" /* 10 */ + "PHPStan\\Type\\Type\0" /* 15 */ + "digits\0" /* 33 */ + "whitespaceFree\0" /* 40 */ + "__construct\0" /* 55 */ + "\0" /* 67 */ + "PHPStan\\Type\\StringSet\0" /* 68 */ + "createEmpty\0" /* 91 */ + "string\0" /* 103 */ + "PHPStan\\Type\\Constant\\ConstantStringType\0" /* 110 */ + "createConstant\0" /* 151 */ + "strings\0" /* 166 */ + "createType\0" /* 174 */ + "toNumberStrings\0" /* 185 */ + "getConstantStrings\0" /* 201 */ + "getType\0" /* 220 */ + "isEmpty\0" /* 228 */ + "other\0" /* 236 */ + "union\0" /* 242 */ + "intersect\0" /* 248 */ + "toType\0" /* 258 */ + "right\0" /* 265 */ + "phpVersion\0" /* 271 */ + "PHPStan\\Php\\PhpVersion\0" /* 282 */ + "concatenate\0" /* 305 */ + "isEmptyString\0" /* 317 */ + "isNonEmpty\0" /* 331 */ + "isNonFalsy\0" /* 342 */ + "isLiteral\0" /* 353 */ + "isLowercase\0" /* 363 */ + "isUppercase\0" /* 375 */ + "isDecimalInteger\0" /* 387 */ + "isNumericOrEmpty\0" /* 404 */ + "canEndInWhitespace\0" /* 421 */ + "afterInteger\0" /* 440 */ + "isNumberSuffix\0" /* 453 */ + "value\0" /* 468 */ + "isNumeric\0" /* 474 */ + "contains"; /* 484 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, MAY_BE_ARRAY), /* __construct $constants */ + reg::packed(10, MAY_BE_NULL, 15), /* __construct $type */ + reg::packed(33, MAY_BE_BOOL), /* __construct $digits */ + reg::packed(40, MAY_BE_BOOL), /* __construct $whitespaceFree */ + reg::packed(67, 0, 68), /* createEmpty return */ + reg::packed(103, 0, 110), /* createConstant $string */ + reg::packed(67, 0, 68), /* createConstant return */ + reg::packed(166, 0, 15), /* createType $strings */ + reg::packed(67, 0, 68), /* createType return */ + reg::packed(33, MAY_BE_BOOL), /* toNumberStrings $digits */ + reg::packed(67, 0, 68), /* toNumberStrings return */ + reg::packed(67, MAY_BE_ARRAY), /* getConstantStrings return */ + reg::packed(67, MAY_BE_NULL, 15), /* getType return */ + reg::packed(67, MAY_BE_BOOL), /* isEmpty return */ + reg::packed(236, 0, 68), /* union $other */ + reg::packed(67, 0, 68), /* union return */ + reg::packed(236, 0, 68), /* intersect $other */ + reg::packed(67, 0, 68), /* intersect return */ + reg::packed(67, 0, 15), /* toType return */ + reg::packed(265, 0, 68), /* concatenate $right */ + reg::packed(271, 0, 282), /* concatenate $phpVersion */ + reg::packed(67, 0, 15), /* concatenate return */ + reg::packed(67, MAY_BE_BOOL), /* isEmptyString return */ + reg::packed(67, MAY_BE_BOOL), /* isNonEmpty return */ + reg::packed(67, MAY_BE_BOOL), /* isNonFalsy return */ + reg::packed(67, MAY_BE_BOOL), /* isLiteral return */ + reg::packed(67, MAY_BE_BOOL), /* isLowercase return */ + reg::packed(67, MAY_BE_BOOL), /* isUppercase return */ + reg::packed(67, MAY_BE_BOOL), /* isDecimalInteger return */ + reg::packed(271, 0, 282), /* isNumericOrEmpty $phpVersion */ + reg::packed(67, MAY_BE_BOOL), /* isNumericOrEmpty return */ + reg::packed(271, 0, 282), /* canEndInWhitespace $phpVersion */ + reg::packed(67, MAY_BE_BOOL), /* canEndInWhitespace return */ + reg::packed(440, MAY_BE_BOOL), /* isNumberSuffix $afterInteger */ + reg::packed(271, 0, 282), /* isNumberSuffix $phpVersion */ + reg::packed(67, MAY_BE_BOOL), /* isNumberSuffix return */ + reg::packed(468, MAY_BE_STRING), /* isNumeric $value */ + reg::packed(271, 0, 282), /* isNumeric $phpVersion */ + reg::packed(67, MAY_BE_BOOL), /* isNumeric return */ + reg::packed(0, MAY_BE_ARRAY), /* contains $constants */ + reg::packed(468, MAY_BE_STRING), /* contains $value */ + reg::packed(67, MAY_BE_BOOL), /* contains return */ +}; +using Sig = reg::Sig; +} // namespace sigtab + +/* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ +namespace sig { +inline constexpr sigtab::Sig __construct = { { 55 /* __construct */, 4, 0, 4, reg::NoArg, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig createEmpty = { { 91 /* createEmpty */, 0, 4, 0, 4, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createConstant = { { 151 /* createConstant */, 1, 5, 1, 6, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createType = { { 174 /* createType */, 1, 7, 1, 8, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toNumberStrings = { { 185 /* toNumberStrings */, 1, 9, 1, 10, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantStrings = { { 201 /* getConstantStrings */, 0, 11, 0, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getType = { { 220 /* getType */, 0, 12, 0, 12, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isEmpty = { { 228 /* isEmpty */, 0, 13, 0, 13, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig union_ = { { 242 /* union */, 1, 14, 1, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig intersect = { { 248 /* intersect */, 1, 16, 1, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toType = { { 258 /* toType */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig concatenate = { { 305 /* concatenate */, 2, 19, 2, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isEmptyString = { { 317 /* isEmptyString */, 0, 22, 0, 22, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isNonEmpty = { { 331 /* isNonEmpty */, 0, 23, 0, 23, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isNonFalsy = { { 342 /* isNonFalsy */, 0, 24, 0, 24, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isLiteral = { { 353 /* isLiteral */, 0, 25, 0, 25, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isLowercase = { { 363 /* isLowercase */, 0, 26, 0, 26, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isUppercase = { { 375 /* isUppercase */, 0, 27, 0, 27, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isDecimalInteger = { { 387 /* isDecimalInteger */, 0, 28, 0, 28, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isNumericOrEmpty = { { 404 /* isNumericOrEmpty */, 1, 29, 1, 30, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig canEndInWhitespace = { { 421 /* canEndInWhitespace */, 1, 31, 1, 32, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isNumberSuffix = { { 453 /* isNumberSuffix */, 2, 33, 2, 35, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isNumeric = { { 474 /* isNumeric */, 2, 36, 2, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig contains = { { 484 /* contains */, 2, 39, 2, 41, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +} // namespace sig + +} // namespace ptdecl::StringSet + +#endif diff --git a/turbo-ext/src/generated/ValueSet.h b/turbo-ext/src/generated/ValueSet.h index 3c9c0286ccf..ebd5d089b3b 100644 --- a/turbo-ext/src/generated/ValueSet.h +++ b/turbo-ext/src/generated/ValueSet.h @@ -15,17 +15,16 @@ inline constexpr uint32_t null = 1; inline constexpr uint32_t false_ = 2; inline constexpr uint32_t true_ = 3; inline constexpr uint32_t numbers = 4; -inline constexpr uint32_t constantStrings = 5; -inline constexpr uint32_t strings = 6; -inline constexpr uint32_t emptyArray = 7; -inline constexpr uint32_t nonEmptyArray = 8; -inline constexpr uint32_t arrays = 9; -inline constexpr uint32_t objects = 10; -inline constexpr uint32_t resources = 11; -inline constexpr uint32_t invalid = 12; -inline constexpr uint32_t benevolent = 13; -inline constexpr uint32_t never = 14; -inline constexpr uint32_t unknownType = 15; +inline constexpr uint32_t strings = 5; +inline constexpr uint32_t emptyArray = 6; +inline constexpr uint32_t nonEmptyArray = 7; +inline constexpr uint32_t arrays = 8; +inline constexpr uint32_t objects = 9; +inline constexpr uint32_t resources = 10; +inline constexpr uint32_t invalid = 11; +inline constexpr uint32_t benevolent = 12; +inline constexpr uint32_t never = 13; +inline constexpr uint32_t unknownType = 14; } // namespace slot inline void declareClass(reg::Class &cls) @@ -41,8 +40,7 @@ inline void declareProperties(reg::Class &cls) cls.property("false", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); cls.property("true", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); cls.property("numbers", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\NumberSet"); - cls.property("constantStrings", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); - cls.property("strings", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); + cls.property("strings", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\StringSet"); cls.property("emptyArray", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); cls.property("nonEmptyArray", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); cls.property("arrays", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); @@ -63,429 +61,448 @@ inline constexpr char strings[] = "true\0" /* 19 */ "numbers\0" /* 24 */ "PHPStan\\Type\\NumberSet\0" /* 32 */ - "constantStrings\0" /* 55 */ - "[]\0" /* 71 */ - "strings\0" /* 74 */ - "PHPStan\\Type\\Type\0" /* 82 */ - "emptyArray\0" /* 100 */ - "nonEmptyArray\0" /* 111 */ - "arrays\0" /* 125 */ - "objects\0" /* 132 */ - "resources\0" /* 140 */ - "invalid\0" /* 150 */ - "benevolent\0" /* 158 */ - "never\0" /* 169 */ - "PHPStan\\Type\\NeverType\0" /* 175 */ - "unknownType\0" /* 198 */ - "__construct\0" /* 210 */ - "type\0" /* 222 */ - "PHPStan\\Type\\MixedType\0" /* 227 */ - "\0" /* 250 */ - "PHPStan\\Type\\ValueSet\0" /* 251 */ - "createUnknown\0" /* 273 */ - "isPlusable\0" /* 287 */ - "isUnknown\0" /* 298 */ - "createEmpty\0" /* 308 */ - "createInvalid\0" /* 320 */ - "createNever\0" /* 334 */ - "createNull\0" /* 346 */ - "createBooleans\0" /* 357 */ - "createNumbers\0" /* 372 */ - "value\0" /* 386 */ - "createConstantString\0" /* 392 */ - "createStrings\0" /* 413 */ - "createArrays\0" /* 427 */ - "createObjects\0" /* 440 */ - "createResources\0" /* 454 */ - "toBenevolent\0" /* 470 */ - "isEmpty\0" /* 483 */ - "other\0" /* 491 */ - "union\0" /* 497 */ - "intersect\0" /* 503 */ - "phpVersion\0" /* 513 */ - "PHPStan\\Php\\PhpVersion\0" /* 524 */ - "getPossiblyLooselyEqualType\0" /* 547 */ - "getAlwaysLooselyEqualType\0" /* 575 */ - "right\0" /* 601 */ - "arithmeticAdd\0" /* 607 */ - "arithmeticSubtract\0" /* 621 */ - "arithmeticMultiply\0" /* 640 */ - "arithmeticDivide\0" /* 659 */ - "arithmeticIntDivide\0" /* 676 */ - "getIntegerNumbers\0" /* 696 */ - "arithmeticModulo\0" /* 714 */ - "exponent\0" /* 731 */ - "arithmeticPower\0" /* 740 */ - "powerMember\0" /* 756 */ - "getMembers\0" /* 768 */ - "toNumberSetOfNumber\0" /* 779 */ - "isOnlyFloats\0" /* 799 */ - "isOnlyFloatNumbers\0" /* 812 */ - "isOnlyInteger\0" /* 831 */ - "createTypeOfNumbers\0" /* 845 */ - "arithmeticNegate\0" /* 865 */ - "bitwiseAnd\0" /* 882 */ - "bitwiseOr\0" /* 893 */ - "bitwiseXor\0" /* 903 */ - "bitwiseShiftLeft\0" /* 914 */ - "bitwiseShiftRight\0" /* 931 */ - "operator\0" /* 949 */ - "calculateShift\0" /* 958 */ - "generalizeTooManyConstants\0" /* 973 */ - "generalize\0" /* 1000 */ - "invalidOrFold\0" /* 1011 */ - "calculateCommonMath\0" /* 1025 */ - "toNumberType\0" /* 1045 */ - "PHPStan\\TrinaryLogic\0" /* 1058 */ - "isArray\0" /* 1079 */ - "isOnlyNonEmptyArrays\0" /* 1087 */ - "calculateBitwise\0" /* 1108 */ - "getResultOfOperands\0" /* 1125 */ - "getZeroDivisorError\0" /* 1145 */ - "result\0" /* 1165 */ - "toResult\0" /* 1172 */ - "fold\0" /* 1181 */ - "left\0" /* 1186 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 1191 */ - "foldValues\0" /* 1223 */ - "base\0" /* 1234 */ - "foldPower\0" /* 1239 */ - "pow\0" /* 1249 */ - "numberType\0" /* 1253 */ - "getConstantNumber\0" /* 1264 */ - "integerType\0" /* 1282 */ - "getConstantInteger\0" /* 1294 */ - "expandIntegerIntervals\0" /* 1313 */ - "getFiniteScalarTypes\0" /* 1336 */ - "withoutArrays\0" /* 1357 */ - "integers\0" /* 1371 */ - "toOperandNumbers\0" /* 1380 */ - "toIntegerNumbers\0" /* 1397 */ - "isOnlyOneIntegerInterval\0" /* 1414 */ - "isOnlyIntegers\0" /* 1439 */ - "isOnlyStrings\0" /* 1454 */ - "mayBeString\0" /* 1468 */ - "mayBeArray\0" /* 1480 */ - "mayBeNonArray\0" /* 1491 */ - "mayBeNonNumber\0" /* 1505 */ - "unionTypes\0" /* 1520 */ - "getNumberValues\0" /* 1531 */ - "getTypeLooselyEqualTo\0" /* 1547 */ - "getLooselyEqualNumber\0" /* 1569 */ - "number\0" /* 1591 */ - "getNumberString\0" /* 1598 */ - "getNumberPartnerType\0" /* 1614 */ - "canBeZero\0" /* 1635 */ - "canBeNonZero\0" /* 1645 */ - "getBooleanAndNullPartnerType\0" /* 1658 */ - "getConstantStringPartnerType\0" /* 1687 */ - "getStringPartnerType\0" /* 1716 */ - "empty\0" /* 1737 */ - "nonEmpty\0" /* 1743 */ - "getArrayPartnerType\0" /* 1752 */ - "createNumberType\0" /* 1772 */ - "min\0" /* 1789 */ - "max\0" /* 1793 */ - "getIntegerTypeInInterval\0" /* 1797 */ - "createNumberLikeObjectType\0" /* 1822 */ - "createStringableObjectType\0" /* 1849 */ - "createNumericStringType"; /* 1876 */ + "strings\0" /* 55 */ + "PHPStan\\Type\\StringSet\0" /* 63 */ + "emptyArray\0" /* 86 */ + "nonEmptyArray\0" /* 97 */ + "arrays\0" /* 111 */ + "PHPStan\\Type\\Type\0" /* 118 */ + "objects\0" /* 136 */ + "resources\0" /* 144 */ + "invalid\0" /* 154 */ + "benevolent\0" /* 162 */ + "never\0" /* 173 */ + "PHPStan\\Type\\NeverType\0" /* 179 */ + "unknownType\0" /* 202 */ + "__construct\0" /* 214 */ + "type\0" /* 226 */ + "PHPStan\\Type\\MixedType\0" /* 231 */ + "\0" /* 254 */ + "PHPStan\\Type\\ValueSet\0" /* 255 */ + "createUnknown\0" /* 277 */ + "isPlusable\0" /* 291 */ + "isUnknown\0" /* 302 */ + "createEmpty\0" /* 312 */ + "createInvalid\0" /* 324 */ + "createNever\0" /* 338 */ + "createNull\0" /* 350 */ + "createBooleans\0" /* 361 */ + "createNumbers\0" /* 376 */ + "string\0" /* 390 */ + "PHPStan\\Type\\Constant\\ConstantStringType\0" /* 397 */ + "createConstantString\0" /* 438 */ + "createStrings\0" /* 459 */ + "createArrays\0" /* 473 */ + "createObjects\0" /* 486 */ + "createResources\0" /* 500 */ + "toBenevolent\0" /* 516 */ + "isEmpty\0" /* 529 */ + "other\0" /* 537 */ + "union\0" /* 543 */ + "intersect\0" /* 549 */ + "phpVersion\0" /* 559 */ + "PHPStan\\Php\\PhpVersion\0" /* 570 */ + "getPossiblyLooselyEqualType\0" /* 593 */ + "getAlwaysLooselyEqualType\0" /* 621 */ + "right\0" /* 647 */ + "arithmeticAdd\0" /* 653 */ + "arithmeticSubtract\0" /* 667 */ + "arithmeticMultiply\0" /* 686 */ + "arithmeticDivide\0" /* 705 */ + "arithmeticIntDivide\0" /* 722 */ + "getIntegerNumbers\0" /* 742 */ + "arithmeticModulo\0" /* 760 */ + "exponent\0" /* 777 */ + "arithmeticPower\0" /* 786 */ + "powerMember\0" /* 802 */ + "stringConcatenate\0" /* 814 */ + "toStringSet\0" /* 832 */ + "toStringSetOfType\0" /* 844 */ + "isOnlyNonNegativeIntegers\0" /* 862 */ + "getConstantStrings\0" /* 888 */ + "getMembers\0" /* 907 */ + "toNumberSetOfNumber\0" /* 918 */ + "isOnlyFloats\0" /* 938 */ + "isOnlyFloatNumbers\0" /* 951 */ + "value\0" /* 970 */ + "isOnlyInteger\0" /* 976 */ + "createTypeOfNumbers\0" /* 990 */ + "arithmeticNegate\0" /* 1010 */ + "bitwiseAnd\0" /* 1027 */ + "bitwiseOr\0" /* 1038 */ + "bitwiseXor\0" /* 1048 */ + "bitwiseShiftLeft\0" /* 1059 */ + "bitwiseShiftRight\0" /* 1076 */ + "operator\0" /* 1094 */ + "calculateShift\0" /* 1103 */ + "generalizeTooManyConstants\0" /* 1118 */ + "generalize\0" /* 1145 */ + "invalidOrFold\0" /* 1156 */ + "calculateCommonMath\0" /* 1170 */ + "toNumberType\0" /* 1190 */ + "PHPStan\\TrinaryLogic\0" /* 1203 */ + "isArray\0" /* 1224 */ + "isOnlyNonEmptyArrays\0" /* 1232 */ + "calculateBitwise\0" /* 1253 */ + "getResultOfOperands\0" /* 1270 */ + "getZeroDivisorError\0" /* 1290 */ + "result\0" /* 1310 */ + "toResult\0" /* 1317 */ + "fold\0" /* 1326 */ + "left\0" /* 1331 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 1336 */ + "foldValues\0" /* 1368 */ + "base\0" /* 1379 */ + "foldPower\0" /* 1384 */ + "pow\0" /* 1394 */ + "numberType\0" /* 1398 */ + "getConstantNumber\0" /* 1409 */ + "integerType\0" /* 1427 */ + "getConstantInteger\0" /* 1439 */ + "expandIntegerIntervals\0" /* 1458 */ + "getFiniteScalarTypes\0" /* 1481 */ + "withoutArrays\0" /* 1502 */ + "integers\0" /* 1516 */ + "toOperandNumbers\0" /* 1525 */ + "toIntegerNumbers\0" /* 1542 */ + "isOnlyOneIntegerInterval\0" /* 1559 */ + "isOnlyIntegers\0" /* 1584 */ + "isOnlyStrings\0" /* 1599 */ + "mayBeString\0" /* 1613 */ + "mayBeArray\0" /* 1625 */ + "mayBeNonArray\0" /* 1636 */ + "mayBeNonNumber\0" /* 1650 */ + "unionTypes\0" /* 1665 */ + "getNumberValues\0" /* 1676 */ + "getTypeLooselyEqualTo\0" /* 1692 */ + "getLooselyEqualNumber\0" /* 1714 */ + "number\0" /* 1736 */ + "getNumberString\0" /* 1743 */ + "getNumberPartnerType\0" /* 1759 */ + "canBeZero\0" /* 1780 */ + "canBeNonZero\0" /* 1790 */ + "getBooleanAndNullPartnerType\0" /* 1803 */ + "getConstantStringPartnerType\0" /* 1832 */ + "getStringPartnerType\0" /* 1861 */ + "empty\0" /* 1882 */ + "nonEmpty\0" /* 1888 */ + "getArrayPartnerType\0" /* 1897 */ + "createNumberType\0" /* 1917 */ + "min\0" /* 1934 */ + "max\0" /* 1938 */ + "getIntegerTypeInInterval\0" /* 1942 */ + "createNumberLikeObjectType\0" /* 1967 */ + "createStringableObjectType\0" /* 1994 */ + "createNumericStringType"; /* 2021 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL), /* __construct $unknown */ reg::packed(8, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $null */ reg::packed(13, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $false */ reg::packed(19, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $true */ reg::packed(24, MAY_BE_NULL, 32, false, false, 8), /* __construct $numbers */ - reg::packed(55, MAY_BE_ARRAY, reg::NoString, false, false, 71), /* __construct $constantStrings */ - reg::packed(74, MAY_BE_NULL, 82, false, false, 8), /* __construct $strings */ - reg::packed(100, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $emptyArray */ - reg::packed(111, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $nonEmptyArray */ - reg::packed(125, MAY_BE_NULL, 82, false, false, 8), /* __construct $arrays */ - reg::packed(132, MAY_BE_NULL, 82, false, false, 8), /* __construct $objects */ - reg::packed(140, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $resources */ - reg::packed(150, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $invalid */ - reg::packed(158, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $benevolent */ - reg::packed(169, MAY_BE_NULL, 175, false, false, 8), /* __construct $never */ - reg::packed(198, MAY_BE_NULL, 82, false, false, 8), /* __construct $unknownType */ - reg::packed(222, MAY_BE_NULL, 227, false, false, 8), /* createUnknown $type */ - reg::packed(250, 0, 251), /* createUnknown return */ - reg::packed(250, MAY_BE_BOOL), /* isPlusable return */ - reg::packed(250, MAY_BE_BOOL), /* isUnknown return */ - reg::packed(250, 0, 251), /* createEmpty return */ - reg::packed(250, 0, 251), /* createInvalid return */ - reg::packed(169, 0, 175), /* createNever $never */ - reg::packed(250, 0, 251), /* createNever return */ - reg::packed(250, 0, 251), /* createNull return */ + reg::packed(55, MAY_BE_NULL, 63, false, false, 8), /* __construct $strings */ + reg::packed(86, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $emptyArray */ + reg::packed(97, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $nonEmptyArray */ + reg::packed(111, MAY_BE_NULL, 118, false, false, 8), /* __construct $arrays */ + reg::packed(136, MAY_BE_NULL, 118, false, false, 8), /* __construct $objects */ + reg::packed(144, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $resources */ + reg::packed(154, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $invalid */ + reg::packed(162, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $benevolent */ + reg::packed(173, MAY_BE_NULL, 179, false, false, 8), /* __construct $never */ + reg::packed(202, MAY_BE_NULL, 118, false, false, 8), /* __construct $unknownType */ + reg::packed(226, MAY_BE_NULL, 231, false, false, 8), /* createUnknown $type */ + reg::packed(254, 0, 255), /* createUnknown return */ + reg::packed(254, MAY_BE_BOOL), /* isPlusable return */ + reg::packed(254, MAY_BE_BOOL), /* isUnknown return */ + reg::packed(254, 0, 255), /* createEmpty return */ + reg::packed(254, 0, 255), /* createInvalid return */ + reg::packed(173, 0, 179), /* createNever $never */ + reg::packed(254, 0, 255), /* createNever return */ + reg::packed(254, 0, 255), /* createNull return */ reg::packed(13, MAY_BE_BOOL), /* createBooleans $false */ reg::packed(19, MAY_BE_BOOL), /* createBooleans $true */ - reg::packed(250, 0, 251), /* createBooleans return */ + reg::packed(254, 0, 255), /* createBooleans return */ reg::packed(24, 0, 32), /* createNumbers $numbers */ - reg::packed(250, 0, 251), /* createNumbers return */ - reg::packed(386, MAY_BE_STRING), /* createConstantString $value */ - reg::packed(250, 0, 251), /* createConstantString return */ - reg::packed(74, 0, 82), /* createStrings $strings */ - reg::packed(250, 0, 251), /* createStrings return */ - reg::packed(125, 0, 82), /* createArrays $arrays */ - reg::packed(250, 0, 251), /* createArrays return */ - reg::packed(132, 0, 82), /* createObjects $objects */ - reg::packed(250, 0, 251), /* createObjects return */ - reg::packed(250, 0, 251), /* createResources return */ - reg::packed(250, 0, 251), /* toBenevolent return */ - reg::packed(250, MAY_BE_BOOL), /* isEmpty return */ - reg::packed(491, 0, 251), /* union $other */ - reg::packed(250, 0, 251), /* union return */ - reg::packed(491, 0, 251), /* intersect $other */ - reg::packed(250, 0, 251), /* intersect return */ - reg::packed(513, 0, 524), /* getPossiblyLooselyEqualType $phpVersion */ - reg::packed(250, 0, 82), /* getPossiblyLooselyEqualType return */ - reg::packed(513, 0, 524), /* getAlwaysLooselyEqualType $phpVersion */ - reg::packed(250, 0, 82), /* getAlwaysLooselyEqualType return */ - reg::packed(601, 0, 251), /* arithmeticAdd $right */ - reg::packed(513, 0, 524), /* arithmeticAdd $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticAdd return */ - reg::packed(601, 0, 251), /* arithmeticSubtract $right */ - reg::packed(513, 0, 524), /* arithmeticSubtract $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticSubtract return */ - reg::packed(601, 0, 251), /* arithmeticMultiply $right */ - reg::packed(513, 0, 524), /* arithmeticMultiply $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticMultiply return */ - reg::packed(601, 0, 251), /* arithmeticDivide $right */ - reg::packed(513, 0, 524), /* arithmeticDivide $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticDivide return */ - reg::packed(601, 0, 251), /* arithmeticIntDivide $right */ - reg::packed(513, 0, 524), /* arithmeticIntDivide $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticIntDivide return */ - reg::packed(250, MAY_BE_NULL, 32), /* getIntegerNumbers return */ - reg::packed(601, 0, 251), /* arithmeticModulo $right */ - reg::packed(513, 0, 524), /* arithmeticModulo $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticModulo return */ - reg::packed(731, 0, 251), /* arithmeticPower $exponent */ - reg::packed(513, 0, 524), /* arithmeticPower $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticPower return */ - reg::packed(731, 0, 251), /* powerMember $exponent */ - reg::packed(513, 0, 524), /* powerMember $phpVersion */ - reg::packed(250, 0, 82), /* powerMember return */ - reg::packed(250, MAY_BE_ARRAY), /* getMembers return */ - reg::packed(250, MAY_BE_NULL, 32), /* toNumberSetOfNumber return */ + reg::packed(254, 0, 255), /* createNumbers return */ + reg::packed(390, 0, 397), /* createConstantString $string */ + reg::packed(254, 0, 255), /* createConstantString return */ + reg::packed(55, 0, 118), /* createStrings $strings */ + reg::packed(254, 0, 255), /* createStrings return */ + reg::packed(111, 0, 118), /* createArrays $arrays */ + reg::packed(254, 0, 255), /* createArrays return */ + reg::packed(136, 0, 118), /* createObjects $objects */ + reg::packed(254, 0, 255), /* createObjects return */ + reg::packed(254, 0, 255), /* createResources return */ + reg::packed(254, 0, 255), /* toBenevolent return */ + reg::packed(254, MAY_BE_BOOL), /* isEmpty return */ + reg::packed(537, 0, 255), /* union $other */ + reg::packed(254, 0, 255), /* union return */ + reg::packed(537, 0, 255), /* intersect $other */ + reg::packed(254, 0, 255), /* intersect return */ + reg::packed(559, 0, 570), /* getPossiblyLooselyEqualType $phpVersion */ + reg::packed(254, 0, 118), /* getPossiblyLooselyEqualType return */ + reg::packed(559, 0, 570), /* getAlwaysLooselyEqualType $phpVersion */ + reg::packed(254, 0, 118), /* getAlwaysLooselyEqualType return */ + reg::packed(647, 0, 255), /* arithmeticAdd $right */ + reg::packed(559, 0, 570), /* arithmeticAdd $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticAdd return */ + reg::packed(647, 0, 255), /* arithmeticSubtract $right */ + reg::packed(559, 0, 570), /* arithmeticSubtract $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticSubtract return */ + reg::packed(647, 0, 255), /* arithmeticMultiply $right */ + reg::packed(559, 0, 570), /* arithmeticMultiply $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticMultiply return */ + reg::packed(647, 0, 255), /* arithmeticDivide $right */ + reg::packed(559, 0, 570), /* arithmeticDivide $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticDivide return */ + reg::packed(647, 0, 255), /* arithmeticIntDivide $right */ + reg::packed(559, 0, 570), /* arithmeticIntDivide $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticIntDivide return */ + reg::packed(254, MAY_BE_NULL, 32), /* getIntegerNumbers return */ + reg::packed(647, 0, 255), /* arithmeticModulo $right */ + reg::packed(559, 0, 570), /* arithmeticModulo $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticModulo return */ + reg::packed(777, 0, 255), /* arithmeticPower $exponent */ + reg::packed(559, 0, 570), /* arithmeticPower $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticPower return */ + reg::packed(777, 0, 255), /* powerMember $exponent */ + reg::packed(559, 0, 570), /* powerMember $phpVersion */ + reg::packed(254, 0, 118), /* powerMember return */ + reg::packed(647, 0, 255), /* stringConcatenate $right */ + reg::packed(559, 0, 570), /* stringConcatenate $phpVersion */ + reg::packed(254, 0, 118), /* stringConcatenate return */ + reg::packed(254, MAY_BE_NULL, 63), /* toStringSet return */ + reg::packed(55, 0, 118), /* toStringSetOfType $strings */ + reg::packed(254, MAY_BE_NULL, 63), /* toStringSetOfType return */ + reg::packed(24, 0, 32), /* isOnlyNonNegativeIntegers $numbers */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyNonNegativeIntegers return */ + reg::packed(254, MAY_BE_ARRAY), /* getConstantStrings return */ + reg::packed(254, MAY_BE_ARRAY), /* getMembers return */ + reg::packed(254, MAY_BE_NULL, 32), /* toNumberSetOfNumber return */ reg::packed(24, MAY_BE_NULL, 32), /* isOnlyFloats $numbers */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyFloats return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyFloats return */ reg::packed(24, 0, 32), /* isOnlyFloatNumbers $numbers */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyFloatNumbers return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyFloatNumbers return */ reg::packed(24, 0, 32), /* isOnlyInteger $numbers */ - reg::packed(386, MAY_BE_LONG), /* isOnlyInteger $value */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyInteger return */ + reg::packed(970, MAY_BE_LONG), /* isOnlyInteger $value */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyInteger return */ reg::packed(24, 0, 32), /* createTypeOfNumbers $numbers */ - reg::packed(250, 0, 82), /* createTypeOfNumbers return */ - reg::packed(513, 0, 524), /* arithmeticNegate $phpVersion */ - reg::packed(250, 0, 82), /* arithmeticNegate return */ - reg::packed(601, 0, 251), /* bitwiseAnd $right */ - reg::packed(513, 0, 524), /* bitwiseAnd $phpVersion */ - reg::packed(250, 0, 82), /* bitwiseAnd return */ - reg::packed(601, 0, 251), /* bitwiseOr $right */ - reg::packed(513, 0, 524), /* bitwiseOr $phpVersion */ - reg::packed(250, 0, 82), /* bitwiseOr return */ - reg::packed(601, 0, 251), /* bitwiseXor $right */ - reg::packed(513, 0, 524), /* bitwiseXor $phpVersion */ - reg::packed(250, 0, 82), /* bitwiseXor return */ - reg::packed(601, 0, 251), /* bitwiseShiftLeft $right */ - reg::packed(513, 0, 524), /* bitwiseShiftLeft $phpVersion */ - reg::packed(250, 0, 82), /* bitwiseShiftLeft return */ - reg::packed(601, 0, 251), /* bitwiseShiftRight $right */ - reg::packed(513, 0, 524), /* bitwiseShiftRight $phpVersion */ - reg::packed(250, 0, 82), /* bitwiseShiftRight return */ - reg::packed(949, MAY_BE_STRING), /* calculateShift $operator */ - reg::packed(601, 0, 251), /* calculateShift $right */ - reg::packed(513, 0, 524), /* calculateShift $phpVersion */ - reg::packed(250, 0, 82), /* calculateShift return */ - reg::packed(601, 0, 251), /* generalizeTooManyConstants $right */ - reg::packed(250, MAY_BE_ARRAY), /* generalizeTooManyConstants return */ - reg::packed(250, 0, 251), /* generalize return */ - reg::packed(949, MAY_BE_STRING), /* invalidOrFold $operator */ - reg::packed(601, 0, 251), /* invalidOrFold $right */ - reg::packed(250, MAY_BE_NULL, 82), /* invalidOrFold return */ - reg::packed(949, MAY_BE_STRING), /* calculateCommonMath $operator */ - reg::packed(601, 0, 251), /* calculateCommonMath $right */ - reg::packed(513, 0, 524), /* calculateCommonMath $phpVersion */ - reg::packed(250, 0, 82), /* calculateCommonMath return */ - reg::packed(250, MAY_BE_NULL, 82), /* toNumberType return */ - reg::packed(250, 0, 1058), /* isArray return */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyNonEmptyArrays return */ - reg::packed(949, MAY_BE_STRING), /* calculateBitwise $operator */ - reg::packed(601, 0, 251), /* calculateBitwise $right */ - reg::packed(513, 0, 524), /* calculateBitwise $phpVersion */ - reg::packed(250, 0, 82), /* calculateBitwise return */ - reg::packed(601, 0, 251), /* getResultOfOperands $right */ - reg::packed(250, MAY_BE_NULL, 82), /* getResultOfOperands return */ - reg::packed(250, MAY_BE_NULL, 82), /* getZeroDivisorError return */ - reg::packed(601, 0, 251), /* toResult $right */ - reg::packed(1165, 0, 82), /* toResult $result */ - reg::packed(250, 0, 82), /* toResult return */ - reg::packed(949, MAY_BE_STRING), /* fold $operator */ - reg::packed(601, 0, 251), /* fold $right */ - reg::packed(250, MAY_BE_NULL, 82), /* fold return */ - reg::packed(949, MAY_BE_STRING), /* foldValues $operator */ - reg::packed(1186, 0, 1191), /* foldValues $left */ - reg::packed(601, 0, 1191), /* foldValues $right */ - reg::packed(250, MAY_BE_NULL, 82), /* foldValues return */ - reg::packed(1234, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $base */ - reg::packed(731, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $exponent */ - reg::packed(250, MAY_BE_NULL, 82), /* foldPower return */ - reg::packed(1234, MAY_BE_ANY), /* pow $base */ - reg::packed(731, MAY_BE_ANY), /* pow $exponent */ - reg::packed(250, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* pow return */ - reg::packed(1253, 0, 82), /* getConstantNumber $numberType */ - reg::packed(250, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getConstantNumber return */ - reg::packed(1282, 0, 82), /* getConstantInteger $integerType */ - reg::packed(250, MAY_BE_NULL | MAY_BE_LONG), /* getConstantInteger return */ - reg::packed(1313, MAY_BE_BOOL), /* getFiniteScalarTypes $expandIntegerIntervals */ - reg::packed(250, MAY_BE_NULL | MAY_BE_ARRAY), /* getFiniteScalarTypes return */ - reg::packed(1357, MAY_BE_BOOL), /* toOperandNumbers $withoutArrays */ - reg::packed(1371, MAY_BE_BOOL), /* toOperandNumbers $integers */ - reg::packed(250, MAY_BE_NULL, 32), /* toOperandNumbers return */ + reg::packed(254, 0, 118), /* createTypeOfNumbers return */ + reg::packed(559, 0, 570), /* arithmeticNegate $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticNegate return */ + reg::packed(647, 0, 255), /* bitwiseAnd $right */ + reg::packed(559, 0, 570), /* bitwiseAnd $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseAnd return */ + reg::packed(647, 0, 255), /* bitwiseOr $right */ + reg::packed(559, 0, 570), /* bitwiseOr $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseOr return */ + reg::packed(647, 0, 255), /* bitwiseXor $right */ + reg::packed(559, 0, 570), /* bitwiseXor $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseXor return */ + reg::packed(647, 0, 255), /* bitwiseShiftLeft $right */ + reg::packed(559, 0, 570), /* bitwiseShiftLeft $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseShiftLeft return */ + reg::packed(647, 0, 255), /* bitwiseShiftRight $right */ + reg::packed(559, 0, 570), /* bitwiseShiftRight $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseShiftRight return */ + reg::packed(1094, MAY_BE_STRING), /* calculateShift $operator */ + reg::packed(647, 0, 255), /* calculateShift $right */ + reg::packed(559, 0, 570), /* calculateShift $phpVersion */ + reg::packed(254, 0, 118), /* calculateShift return */ + reg::packed(647, 0, 255), /* generalizeTooManyConstants $right */ + reg::packed(254, MAY_BE_ARRAY), /* generalizeTooManyConstants return */ + reg::packed(254, 0, 255), /* generalize return */ + reg::packed(1094, MAY_BE_STRING), /* invalidOrFold $operator */ + reg::packed(647, 0, 255), /* invalidOrFold $right */ + reg::packed(254, MAY_BE_NULL, 118), /* invalidOrFold return */ + reg::packed(1094, MAY_BE_STRING), /* calculateCommonMath $operator */ + reg::packed(647, 0, 255), /* calculateCommonMath $right */ + reg::packed(559, 0, 570), /* calculateCommonMath $phpVersion */ + reg::packed(254, 0, 118), /* calculateCommonMath return */ + reg::packed(254, MAY_BE_NULL, 118), /* toNumberType return */ + reg::packed(254, 0, 1203), /* isArray return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyNonEmptyArrays return */ + reg::packed(1094, MAY_BE_STRING), /* calculateBitwise $operator */ + reg::packed(647, 0, 255), /* calculateBitwise $right */ + reg::packed(559, 0, 570), /* calculateBitwise $phpVersion */ + reg::packed(254, 0, 118), /* calculateBitwise return */ + reg::packed(647, 0, 255), /* getResultOfOperands $right */ + reg::packed(254, MAY_BE_NULL, 118), /* getResultOfOperands return */ + reg::packed(254, MAY_BE_NULL, 118), /* getZeroDivisorError return */ + reg::packed(647, 0, 255), /* toResult $right */ + reg::packed(1310, 0, 118), /* toResult $result */ + reg::packed(254, 0, 118), /* toResult return */ + reg::packed(1094, MAY_BE_STRING), /* fold $operator */ + reg::packed(647, 0, 255), /* fold $right */ + reg::packed(254, MAY_BE_NULL, 118), /* fold return */ + reg::packed(1094, MAY_BE_STRING), /* foldValues $operator */ + reg::packed(1331, 0, 1336), /* foldValues $left */ + reg::packed(647, 0, 1336), /* foldValues $right */ + reg::packed(254, MAY_BE_NULL, 118), /* foldValues return */ + reg::packed(1379, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $base */ + reg::packed(777, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $exponent */ + reg::packed(254, MAY_BE_NULL, 118), /* foldPower return */ + reg::packed(1379, MAY_BE_ANY), /* pow $base */ + reg::packed(777, MAY_BE_ANY), /* pow $exponent */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* pow return */ + reg::packed(1398, 0, 118), /* getConstantNumber $numberType */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getConstantNumber return */ + reg::packed(1427, 0, 118), /* getConstantInteger $integerType */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG), /* getConstantInteger return */ + reg::packed(1458, MAY_BE_BOOL), /* getFiniteScalarTypes $expandIntegerIntervals */ + reg::packed(254, MAY_BE_NULL | MAY_BE_ARRAY), /* getFiniteScalarTypes return */ + reg::packed(1502, MAY_BE_BOOL), /* toOperandNumbers $withoutArrays */ + reg::packed(1516, MAY_BE_BOOL), /* toOperandNumbers $integers */ + reg::packed(254, MAY_BE_NULL, 32), /* toOperandNumbers return */ reg::packed(24, 0, 32), /* toIntegerNumbers $numbers */ - reg::packed(250, 0, 32), /* toIntegerNumbers return */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyOneIntegerInterval return */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyIntegers return */ - reg::packed(250, MAY_BE_BOOL), /* isOnlyStrings return */ - reg::packed(250, MAY_BE_BOOL), /* mayBeString return */ - reg::packed(250, MAY_BE_BOOL), /* mayBeArray return */ - reg::packed(250, MAY_BE_BOOL), /* mayBeNonArray return */ - reg::packed(250, MAY_BE_BOOL), /* mayBeNonNumber return */ - reg::packed(1186, MAY_BE_NULL, 82), /* unionTypes $left */ - reg::packed(601, MAY_BE_NULL, 82), /* unionTypes $right */ - reg::packed(250, MAY_BE_NULL, 82), /* unionTypes return */ + reg::packed(254, 0, 32), /* toIntegerNumbers return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyOneIntegerInterval return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyIntegers return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyStrings return */ + reg::packed(254, MAY_BE_BOOL), /* mayBeString return */ + reg::packed(254, MAY_BE_BOOL), /* mayBeArray return */ + reg::packed(254, MAY_BE_BOOL), /* mayBeNonArray return */ + reg::packed(254, MAY_BE_BOOL), /* mayBeNonNumber return */ + reg::packed(1331, MAY_BE_NULL, 118), /* unionTypes $left */ + reg::packed(647, MAY_BE_NULL, 118), /* unionTypes $right */ + reg::packed(254, MAY_BE_NULL, 118), /* unionTypes return */ reg::packed(24, 0, 32), /* getNumberValues $numbers */ - reg::packed(250, MAY_BE_NULL | MAY_BE_ARRAY), /* getNumberValues return */ - reg::packed(386, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getTypeLooselyEqualTo $value */ - reg::packed(513, 0, 524), /* getTypeLooselyEqualTo $phpVersion */ - reg::packed(250, 0, 82), /* getTypeLooselyEqualTo return */ - reg::packed(386, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getLooselyEqualNumber $value */ - reg::packed(513, 0, 524), /* getLooselyEqualNumber $phpVersion */ - reg::packed(250, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getLooselyEqualNumber return */ - reg::packed(1591, MAY_BE_LONG | MAY_BE_DOUBLE), /* getNumberString $number */ - reg::packed(250, MAY_BE_STRING), /* getNumberString return */ + reg::packed(254, MAY_BE_NULL | MAY_BE_ARRAY), /* getNumberValues return */ + reg::packed(970, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getTypeLooselyEqualTo $value */ + reg::packed(559, 0, 570), /* getTypeLooselyEqualTo $phpVersion */ + reg::packed(254, 0, 118), /* getTypeLooselyEqualTo return */ + reg::packed(970, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getLooselyEqualNumber $value */ + reg::packed(559, 0, 570), /* getLooselyEqualNumber $phpVersion */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getLooselyEqualNumber return */ + reg::packed(1736, MAY_BE_LONG | MAY_BE_DOUBLE), /* getNumberString $number */ + reg::packed(254, MAY_BE_STRING), /* getNumberString return */ reg::packed(24, 0, 32), /* getNumberPartnerType $numbers */ - reg::packed(513, 0, 524), /* getNumberPartnerType $phpVersion */ - reg::packed(250, 0, 82), /* getNumberPartnerType return */ + reg::packed(559, 0, 570), /* getNumberPartnerType $phpVersion */ + reg::packed(254, 0, 118), /* getNumberPartnerType return */ reg::packed(24, 0, 32), /* canBeZero $numbers */ - reg::packed(250, MAY_BE_BOOL), /* canBeZero return */ + reg::packed(254, MAY_BE_BOOL), /* canBeZero return */ reg::packed(24, 0, 32), /* canBeNonZero $numbers */ - reg::packed(250, MAY_BE_BOOL), /* canBeNonZero return */ - reg::packed(1635, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeZero */ - reg::packed(1645, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeNonZero */ - reg::packed(250, 0, 82), /* getBooleanAndNullPartnerType return */ - reg::packed(386, MAY_BE_STRING), /* getConstantStringPartnerType $value */ - reg::packed(513, 0, 524), /* getConstantStringPartnerType $phpVersion */ - reg::packed(250, 0, 82), /* getConstantStringPartnerType return */ - reg::packed(74, 0, 82), /* getStringPartnerType $strings */ - reg::packed(513, 0, 524), /* getStringPartnerType $phpVersion */ - reg::packed(250, 0, 82), /* getStringPartnerType return */ - reg::packed(1737, MAY_BE_BOOL), /* getArrayPartnerType $empty */ - reg::packed(1743, MAY_BE_BOOL), /* getArrayPartnerType $nonEmpty */ - reg::packed(250, 0, 82), /* getArrayPartnerType return */ - reg::packed(1591, MAY_BE_LONG | MAY_BE_DOUBLE), /* createNumberType $number */ - reg::packed(250, 0, 82), /* createNumberType return */ - reg::packed(1789, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $min */ - reg::packed(1793, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $max */ - reg::packed(250, 0, 82), /* getIntegerTypeInInterval return */ - reg::packed(513, 0, 524), /* createNumberLikeObjectType $phpVersion */ - reg::packed(250, 0, 82), /* createNumberLikeObjectType return */ - reg::packed(513, 0, 524), /* createStringableObjectType $phpVersion */ - reg::packed(250, 0, 82), /* createStringableObjectType return */ - reg::packed(250, 0, 82), /* createNumericStringType return */ + reg::packed(254, MAY_BE_BOOL), /* canBeNonZero return */ + reg::packed(1780, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeZero */ + reg::packed(1790, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeNonZero */ + reg::packed(254, 0, 118), /* getBooleanAndNullPartnerType return */ + reg::packed(970, MAY_BE_STRING), /* getConstantStringPartnerType $value */ + reg::packed(559, 0, 570), /* getConstantStringPartnerType $phpVersion */ + reg::packed(254, 0, 118), /* getConstantStringPartnerType return */ + reg::packed(55, 0, 118), /* getStringPartnerType $strings */ + reg::packed(559, 0, 570), /* getStringPartnerType $phpVersion */ + reg::packed(254, 0, 118), /* getStringPartnerType return */ + reg::packed(1882, MAY_BE_BOOL), /* getArrayPartnerType $empty */ + reg::packed(1888, MAY_BE_BOOL), /* getArrayPartnerType $nonEmpty */ + reg::packed(254, 0, 118), /* getArrayPartnerType return */ + reg::packed(1736, MAY_BE_LONG | MAY_BE_DOUBLE), /* createNumberType $number */ + reg::packed(254, 0, 118), /* createNumberType return */ + reg::packed(1934, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $min */ + reg::packed(1938, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $max */ + reg::packed(254, 0, 118), /* getIntegerTypeInInterval return */ + reg::packed(559, 0, 570), /* createNumberLikeObjectType $phpVersion */ + reg::packed(254, 0, 118), /* createNumberLikeObjectType return */ + reg::packed(559, 0, 570), /* createStringableObjectType $phpVersion */ + reg::packed(254, 0, 118), /* createStringableObjectType return */ + reg::packed(254, 0, 118), /* createNumericStringType return */ }; using Sig = reg::Sig; } // namespace sigtab /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { -inline constexpr sigtab::Sig __construct = { { 210 /* __construct */, 1, 0, 16, reg::NoArg, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig createUnknown = { { 273 /* createUnknown */, 0, 16, 1, 17, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isPlusable = { { 287 /* isPlusable */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isUnknown = { { 298 /* isUnknown */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig createEmpty = { { 308 /* createEmpty */, 0, 20, 0, 20, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createInvalid = { { 320 /* createInvalid */, 0, 21, 0, 21, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNever = { { 334 /* createNever */, 1, 22, 1, 23, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNull = { { 346 /* createNull */, 0, 24, 0, 24, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createBooleans = { { 357 /* createBooleans */, 2, 25, 2, 27, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumbers = { { 372 /* createNumbers */, 1, 28, 1, 29, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createConstantString = { { 392 /* createConstantString */, 1, 30, 1, 31, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createStrings = { { 413 /* createStrings */, 1, 32, 1, 33, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createArrays = { { 427 /* createArrays */, 1, 34, 1, 35, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createObjects = { { 440 /* createObjects */, 1, 36, 1, 37, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createResources = { { 454 /* createResources */, 0, 38, 0, 38, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toBenevolent = { { 470 /* toBenevolent */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isEmpty = { { 483 /* isEmpty */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig union_ = { { 497 /* union */, 1, 41, 1, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig intersect = { { 503 /* intersect */, 1, 43, 1, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 547 /* getPossiblyLooselyEqualType */, 1, 45, 1, 46, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 575 /* getAlwaysLooselyEqualType */, 1, 47, 1, 48, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticAdd = { { 607 /* arithmeticAdd */, 2, 49, 2, 51, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticSubtract = { { 621 /* arithmeticSubtract */, 2, 52, 2, 54, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticMultiply = { { 640 /* arithmeticMultiply */, 2, 55, 2, 57, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticDivide = { { 659 /* arithmeticDivide */, 2, 58, 2, 60, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticIntDivide = { { 676 /* arithmeticIntDivide */, 2, 61, 2, 63, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getIntegerNumbers = { { 696 /* getIntegerNumbers */, 0, 64, 0, 64, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig arithmeticModulo = { { 714 /* arithmeticModulo */, 2, 65, 2, 67, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticPower = { { 740 /* arithmeticPower */, 2, 68, 2, 70, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig powerMember = { { 756 /* powerMember */, 2, 71, 2, 73, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getMembers = { { 768 /* getMembers */, 0, 74, 0, 74, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toNumberSetOfNumber = { { 779 /* toNumberSetOfNumber */, 0, 75, 0, 75, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyFloats = { { 799 /* isOnlyFloats */, 1, 76, 1, 77, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyFloatNumbers = { { 812 /* isOnlyFloatNumbers */, 1, 78, 1, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isOnlyInteger = { { 831 /* isOnlyInteger */, 2, 80, 2, 82, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createTypeOfNumbers = { { 845 /* createTypeOfNumbers */, 1, 83, 1, 84, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arithmeticNegate = { { 865 /* arithmeticNegate */, 1, 85, 1, 86, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseAnd = { { 882 /* bitwiseAnd */, 2, 87, 2, 89, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseOr = { { 893 /* bitwiseOr */, 2, 90, 2, 92, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseXor = { { 903 /* bitwiseXor */, 2, 93, 2, 95, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftLeft = { { 914 /* bitwiseShiftLeft */, 2, 96, 2, 98, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftRight = { { 931 /* bitwiseShiftRight */, 2, 99, 2, 101, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig calculateShift = { { 958 /* calculateShift */, 3, 102, 3, 105, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig generalizeTooManyConstants = { { 973 /* generalizeTooManyConstants */, 1, 106, 1, 107, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig generalize = { { 1000 /* generalize */, 0, 108, 0, 108, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig invalidOrFold = { { 1011 /* invalidOrFold */, 2, 109, 2, 111, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig calculateCommonMath = { { 1025 /* calculateCommonMath */, 3, 112, 3, 115, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toNumberType = { { 1045 /* toNumberType */, 0, 116, 0, 116, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isArray = { { 1079 /* isArray */, 0, 117, 0, 117, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyNonEmptyArrays = { { 1087 /* isOnlyNonEmptyArrays */, 0, 118, 0, 118, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig calculateBitwise = { { 1108 /* calculateBitwise */, 3, 119, 3, 122, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getResultOfOperands = { { 1125 /* getResultOfOperands */, 1, 123, 1, 124, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getZeroDivisorError = { { 1145 /* getZeroDivisorError */, 0, 125, 0, 125, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toResult = { { 1172 /* toResult */, 2, 126, 2, 128, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig fold = { { 1181 /* fold */, 2, 129, 2, 131, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig foldValues = { { 1223 /* foldValues */, 3, 132, 3, 135, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig foldPower = { { 1239 /* foldPower */, 2, 136, 2, 138, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig pow = { { 1249 /* pow */, 2, 139, 2, 141, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantNumber = { { 1264 /* getConstantNumber */, 1, 142, 1, 143, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantInteger = { { 1294 /* getConstantInteger */, 1, 144, 1, 145, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFiniteScalarTypes = { { 1336 /* getFiniteScalarTypes */, 1, 146, 1, 147, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toOperandNumbers = { { 1380 /* toOperandNumbers */, 2, 148, 2, 150, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toIntegerNumbers = { { 1397 /* toIntegerNumbers */, 1, 151, 1, 152, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isOnlyOneIntegerInterval = { { 1414 /* isOnlyOneIntegerInterval */, 0, 153, 0, 153, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyIntegers = { { 1439 /* isOnlyIntegers */, 0, 154, 0, 154, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyStrings = { { 1454 /* isOnlyStrings */, 0, 155, 0, 155, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeString = { { 1468 /* mayBeString */, 0, 156, 0, 156, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeArray = { { 1480 /* mayBeArray */, 0, 157, 0, 157, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeNonArray = { { 1491 /* mayBeNonArray */, 0, 158, 0, 158, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeNonNumber = { { 1505 /* mayBeNonNumber */, 0, 159, 0, 159, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig unionTypes = { { 1520 /* unionTypes */, 2, 160, 2, 162, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNumberValues = { { 1531 /* getNumberValues */, 1, 163, 1, 164, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getTypeLooselyEqualTo = { { 1547 /* getTypeLooselyEqualTo */, 2, 165, 2, 167, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getLooselyEqualNumber = { { 1569 /* getLooselyEqualNumber */, 2, 168, 2, 170, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNumberString = { { 1598 /* getNumberString */, 1, 171, 1, 172, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNumberPartnerType = { { 1614 /* getNumberPartnerType */, 2, 173, 2, 175, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig canBeZero = { { 1635 /* canBeZero */, 1, 176, 1, 177, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig canBeNonZero = { { 1645 /* canBeNonZero */, 1, 178, 1, 179, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getBooleanAndNullPartnerType = { { 1658 /* getBooleanAndNullPartnerType */, 2, 180, 2, 182, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantStringPartnerType = { { 1687 /* getConstantStringPartnerType */, 2, 183, 2, 185, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getStringPartnerType = { { 1716 /* getStringPartnerType */, 2, 186, 2, 188, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getArrayPartnerType = { { 1752 /* getArrayPartnerType */, 2, 189, 2, 191, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumberType = { { 1772 /* createNumberType */, 1, 192, 1, 193, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerTypeInInterval = { { 1797 /* getIntegerTypeInInterval */, 2, 194, 2, 196, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumberLikeObjectType = { { 1822 /* createNumberLikeObjectType */, 1, 197, 1, 198, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createStringableObjectType = { { 1849 /* createStringableObjectType */, 1, 199, 1, 200, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumericStringType = { { 1876 /* createNumericStringType */, 0, 201, 0, 201, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig __construct = { { 214 /* __construct */, 1, 0, 15, reg::NoArg, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig createUnknown = { { 277 /* createUnknown */, 0, 15, 1, 16, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isPlusable = { { 291 /* isPlusable */, 0, 17, 0, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUnknown = { { 302 /* isUnknown */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig createEmpty = { { 312 /* createEmpty */, 0, 19, 0, 19, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createInvalid = { { 324 /* createInvalid */, 0, 20, 0, 20, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNever = { { 338 /* createNever */, 1, 21, 1, 22, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNull = { { 350 /* createNull */, 0, 23, 0, 23, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createBooleans = { { 361 /* createBooleans */, 2, 24, 2, 26, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumbers = { { 376 /* createNumbers */, 1, 27, 1, 28, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createConstantString = { { 438 /* createConstantString */, 1, 29, 1, 30, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createStrings = { { 459 /* createStrings */, 1, 31, 1, 32, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createArrays = { { 473 /* createArrays */, 1, 33, 1, 34, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createObjects = { { 486 /* createObjects */, 1, 35, 1, 36, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createResources = { { 500 /* createResources */, 0, 37, 0, 37, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toBenevolent = { { 516 /* toBenevolent */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isEmpty = { { 529 /* isEmpty */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig union_ = { { 543 /* union */, 1, 40, 1, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig intersect = { { 549 /* intersect */, 1, 42, 1, 43, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 593 /* getPossiblyLooselyEqualType */, 1, 44, 1, 45, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 621 /* getAlwaysLooselyEqualType */, 1, 46, 1, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticAdd = { { 653 /* arithmeticAdd */, 2, 48, 2, 50, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticSubtract = { { 667 /* arithmeticSubtract */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticMultiply = { { 686 /* arithmeticMultiply */, 2, 54, 2, 56, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticDivide = { { 705 /* arithmeticDivide */, 2, 57, 2, 59, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticIntDivide = { { 722 /* arithmeticIntDivide */, 2, 60, 2, 62, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getIntegerNumbers = { { 742 /* getIntegerNumbers */, 0, 63, 0, 63, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig arithmeticModulo = { { 760 /* arithmeticModulo */, 2, 64, 2, 66, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 786 /* arithmeticPower */, 2, 67, 2, 69, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig powerMember = { { 802 /* powerMember */, 2, 70, 2, 72, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig stringConcatenate = { { 814 /* stringConcatenate */, 2, 73, 2, 75, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toStringSet = { { 832 /* toStringSet */, 0, 76, 0, 76, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toStringSetOfType = { { 844 /* toStringSetOfType */, 1, 77, 1, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyNonNegativeIntegers = { { 862 /* isOnlyNonNegativeIntegers */, 1, 79, 1, 80, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantStrings = { { 888 /* getConstantStrings */, 0, 81, 0, 81, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getMembers = { { 907 /* getMembers */, 0, 82, 0, 82, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toNumberSetOfNumber = { { 918 /* toNumberSetOfNumber */, 0, 83, 0, 83, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyFloats = { { 938 /* isOnlyFloats */, 1, 84, 1, 85, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyFloatNumbers = { { 951 /* isOnlyFloatNumbers */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyInteger = { { 976 /* isOnlyInteger */, 2, 88, 2, 90, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createTypeOfNumbers = { { 990 /* createTypeOfNumbers */, 1, 91, 1, 92, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arithmeticNegate = { { 1010 /* arithmeticNegate */, 1, 93, 1, 94, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 1027 /* bitwiseAnd */, 2, 95, 2, 97, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 1038 /* bitwiseOr */, 2, 98, 2, 100, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 1048 /* bitwiseXor */, 2, 101, 2, 103, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftLeft = { { 1059 /* bitwiseShiftLeft */, 2, 104, 2, 106, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftRight = { { 1076 /* bitwiseShiftRight */, 2, 107, 2, 109, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculateShift = { { 1103 /* calculateShift */, 3, 110, 3, 113, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeTooManyConstants = { { 1118 /* generalizeTooManyConstants */, 1, 114, 1, 115, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalize = { { 1145 /* generalize */, 0, 116, 0, 116, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invalidOrFold = { { 1156 /* invalidOrFold */, 2, 117, 2, 119, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig calculateCommonMath = { { 1170 /* calculateCommonMath */, 3, 120, 3, 123, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toNumberType = { { 1190 /* toNumberType */, 0, 124, 0, 124, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isArray = { { 1224 /* isArray */, 0, 125, 0, 125, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyNonEmptyArrays = { { 1232 /* isOnlyNonEmptyArrays */, 0, 126, 0, 126, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig calculateBitwise = { { 1253 /* calculateBitwise */, 3, 127, 3, 130, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getResultOfOperands = { { 1270 /* getResultOfOperands */, 1, 131, 1, 132, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getZeroDivisorError = { { 1290 /* getZeroDivisorError */, 0, 133, 0, 133, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toResult = { { 1317 /* toResult */, 2, 134, 2, 136, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig fold = { { 1326 /* fold */, 2, 137, 2, 139, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig foldValues = { { 1368 /* foldValues */, 3, 140, 3, 143, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig foldPower = { { 1384 /* foldPower */, 2, 144, 2, 146, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig pow = { { 1394 /* pow */, 2, 147, 2, 149, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantNumber = { { 1409 /* getConstantNumber */, 1, 150, 1, 151, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantInteger = { { 1439 /* getConstantInteger */, 1, 152, 1, 153, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFiniteScalarTypes = { { 1481 /* getFiniteScalarTypes */, 1, 154, 1, 155, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toOperandNumbers = { { 1525 /* toOperandNumbers */, 2, 156, 2, 158, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toIntegerNumbers = { { 1542 /* toIntegerNumbers */, 1, 159, 1, 160, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyOneIntegerInterval = { { 1559 /* isOnlyOneIntegerInterval */, 0, 161, 0, 161, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyIntegers = { { 1584 /* isOnlyIntegers */, 0, 162, 0, 162, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyStrings = { { 1599 /* isOnlyStrings */, 0, 163, 0, 163, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeString = { { 1613 /* mayBeString */, 0, 164, 0, 164, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeArray = { { 1625 /* mayBeArray */, 0, 165, 0, 165, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeNonArray = { { 1636 /* mayBeNonArray */, 0, 166, 0, 166, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeNonNumber = { { 1650 /* mayBeNonNumber */, 0, 167, 0, 167, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig unionTypes = { { 1665 /* unionTypes */, 2, 168, 2, 170, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberValues = { { 1676 /* getNumberValues */, 1, 171, 1, 172, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getTypeLooselyEqualTo = { { 1692 /* getTypeLooselyEqualTo */, 2, 173, 2, 175, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getLooselyEqualNumber = { { 1714 /* getLooselyEqualNumber */, 2, 176, 2, 178, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberString = { { 1743 /* getNumberString */, 1, 179, 1, 180, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberPartnerType = { { 1759 /* getNumberPartnerType */, 2, 181, 2, 183, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canBeZero = { { 1780 /* canBeZero */, 1, 184, 1, 185, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canBeNonZero = { { 1790 /* canBeNonZero */, 1, 186, 1, 187, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getBooleanAndNullPartnerType = { { 1803 /* getBooleanAndNullPartnerType */, 2, 188, 2, 190, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantStringPartnerType = { { 1832 /* getConstantStringPartnerType */, 2, 191, 2, 193, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getStringPartnerType = { { 1861 /* getStringPartnerType */, 2, 194, 2, 196, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getArrayPartnerType = { { 1897 /* getArrayPartnerType */, 2, 197, 2, 199, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumberType = { { 1917 /* createNumberType */, 1, 200, 1, 201, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerTypeInInterval = { { 1942 /* getIntegerTypeInInterval */, 2, 202, 2, 204, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumberLikeObjectType = { { 1967 /* createNumberLikeObjectType */, 1, 205, 1, 206, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createStringableObjectType = { { 1994 /* createStringableObjectType */, 1, 207, 1, 208, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumericStringType = { { 2021 /* createNumericStringType */, 0, 209, 0, 209, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::ValueSet diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index f49ff208f79..03644f18991 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -4100,10 +4100,9 @@ extern zend_class_entry *pt_ce_operator_type_helper; /* the operators OperatorTypeTrait still hands to OperatorTypeHelper */ enum pt_operator_type_op : uint8_t { - PT_OPERATOR_CONCATENATE, PT_OPERATOR_ARRAY_UNION, }; -/* OperatorTypeHelper::concat() / arrayUnion()($left, $right, $phpVersion) +/* OperatorTypeHelper::arrayUnion($left, $right, $phpVersion) * (all borrowed); UNDEF = pending exception */ zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion); diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index b60b91f1445..c632b6d995d 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -1121,6 +1121,7 @@ public function enterNode(\PhpParser\Node $node) $covered[\PHPStan\Reflection\InitializerExprTypeResolver::class] = true; $covered[\PHPStan\Type\OperatorTypeHelper::class] = true; $covered[\PHPStan\Type\ValueSet::class] = true; +$covered[\PHPStan\Type\StringSet::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'); @@ -1193,7 +1194,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\FloatRangeType::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\Type\ValueSet::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\FloatRangeType::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\Type\ValueSet::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, \PHPStan\Type\StringSet::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 1d682b64880..5a23bc8c8da 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7791,7 +7791,8 @@ 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, 'concat'))->isInternal(); +$observations['native PHPStan\Type\OperatorTypeHelper'] = (new ReflectionMethod(\PHPStan\Type\OperatorTypeHelper::class, 'arrayUnion'))->isInternal(); +$observations['native PHPStan\Type\StringSet'] = (new ReflectionMethod(\PHPStan\Type\StringSet::class, 'concatenate'))->isInternal(); $observations['native PHPStan\Type\ValueSet'] = (new ReflectionMethod(\PHPStan\Type\ValueSet::class, 'union'))->isInternal(); { $resolver = $stringContainer->getByType(\PHPStan\Reflection\InitializerExprTypeResolver::class); @@ -7938,6 +7939,34 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu $r["valueSet intersect $leftName $rightName"] = $catching(static fn () => $leftSet->intersect($right->toValueSet())->getPossiblyLooselyEqualType(new \PHPStan\Php\PhpVersion(80500))); } } + // StringSet on its own: the factories, union and intersection, and . on the sets + $numericString = new \PHPStan\Type\IntersectionType([new \PHPStan\Type\StringType(), new \PHPStan\Type\Accessory\AccessoryNumericStringType()]); + $decimalString = new \PHPStan\Type\IntersectionType([new \PHPStan\Type\StringType(), new \PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType()]); + $stringSets = [ + 'empty' => \PHPStan\Type\StringSet::createEmpty(), + "''" => \PHPStan\Type\StringSet::createConstant($str('')), + "'0'" => \PHPStan\Type\StringSet::createConstant($str('0')), + "'5'" => \PHPStan\Type\StringSet::createConstant($str('5')), + "'1.5e3'" => \PHPStan\Type\StringSet::createConstant($str('1.5e3')), + "'1 '" => \PHPStan\Type\StringSet::createConstant($str('1 ')), + "'Abc'" => \PHPStan\Type\StringSet::createConstant($str('Abc')), + 'string' => \PHPStan\Type\StringSet::createType(new \PHPStan\Type\StringType()), + 'numeric' => \PHPStan\Type\StringSet::createType($numericString), + 'decimal' => \PHPStan\Type\StringSet::createType($decimalString), + 'numeric numbers' => \PHPStan\Type\StringSet::createType($numericString)->toNumberStrings(false), + 'digits' => \PHPStan\Type\StringSet::createType($decimalString)->toNumberStrings(true), + ]; + foreach ($stringSets as $leftName => $leftStrings) { + $r["stringSet $leftName"] = [$leftStrings->isEmpty(), count($leftStrings->getConstantStrings()), $catching(static fn () => $leftStrings->getType()), $catching(static fn () => $leftStrings->toType())]; + foreach ($stringSets as $rightName => $rightStrings) { + $r["stringSet union $leftName $rightName"] = $catching(static fn () => $leftStrings->union($rightStrings)->toType()); + $r["stringSet intersect $leftName $rightName"] = $catching(static fn () => $leftStrings->intersect($rightStrings)->toType()); + foreach ([70400, 80500] as $concatenateVersionId) { + $r["stringSet concatenate $concatenateVersionId $leftName $rightName"] = $catching(static fn () => $leftStrings->concatenate($rightStrings, new \PHPStan\Php\PhpVersion($concatenateVersionId))); + $r["stringSet concatenate union $concatenateVersionId $leftName $rightName"] = $catching(static fn () => $leftStrings->union($stringSets["'5'"])->concatenate($rightStrings->union($stringSets['digits']), new \PHPStan\Php\PhpVersion($concatenateVersionId))); + } + } + } foreach ($operands as $name => $operand) { $r["exponentiate $name int7"] = $catching(static fn () => $operand->exponentiate($int(7))); } From 5966a36e3313409aa9028e8724bd4330f310ef63 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 07:24:31 +0000 Subject: [PATCH 07/15] Compute the array + operator in OperatorTypeTrait OperatorTypeTrait::arrayUnion() unions the arrays itself, the last operator OperatorTypeHelper computed, which is removed. Its native body moves to OperatorTypeTrait.cpp. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/OperatorTypeHelper.php | 163 --------- src/Type/Traits/OperatorTypeTrait.php | 143 +++++++- turbo-ext/src/IetrSupport.h | 4 +- turbo-ext/src/OperatorTypeHelper.cpp | 334 ------------------- turbo-ext/src/OperatorTypeTrait.cpp | 208 ++++++++++++ turbo-ext/src/TypeTraits.cpp | 16 +- turbo-ext/src/TypeTraits.h | 6 +- turbo-ext/src/generated/OperatorTypeHelper.h | 53 --- turbo-ext/src/support.h | 15 +- turbo-ext/tests/smoke.php | 3 +- turbo-ext/tests/type-family.php | 1 - 11 files changed, 365 insertions(+), 581 deletions(-) delete mode 100644 src/Type/OperatorTypeHelper.php delete mode 100644 turbo-ext/src/OperatorTypeHelper.cpp create mode 100644 turbo-ext/src/OperatorTypeTrait.cpp delete mode 100644 turbo-ext/src/generated/OperatorTypeHelper.h diff --git a/src/Type/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php deleted file mode 100644 index 31d826050ab..00000000000 --- a/src/Type/OperatorTypeHelper.php +++ /dev/null @@ -1,163 +0,0 @@ -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; - } - -} diff --git a/src/Type/Traits/OperatorTypeTrait.php b/src/Type/Traits/OperatorTypeTrait.php index 075cb5d1cda..70eed1b6e65 100644 --- a/src/Type/Traits/OperatorTypeTrait.php +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -4,11 +4,17 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; +use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\HasOffsetValueType; +use PHPStan\Type\Accessory\NonEmptyArrayType; +use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\ErrorType; use PHPStan\Type\NumberSet; -use PHPStan\Type\OperatorTypeHelper; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\ValueSet; +use function count; trait OperatorTypeTrait { @@ -127,7 +133,140 @@ public function stringConcatenate(Type $right, PhpVersion $phpVersion): Type public function arrayUnion(Type $right, PhpVersion $phpVersion): Type { - return OperatorTypeHelper::arrayUnion($this, $right, $phpVersion); + $leftConstantArrays = $this->getConstantArrays(); + $rightConstantArrays = $right->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 + && $right->isArray()->yes()) { + $allSealednessKnown = true; + foreach ($leftConstantArrays as $leftConstantArray) { + if ($leftConstantArray->isUnsealed()->maybe()) { + $allSealednessKnown = false; + break; + } + } + + if ($allSealednessKnown) { + $rightKeyType = $right->getIterableKeyType(); + $rightValueType = $right->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 = $this->isArray(); + $rightIsArray = $right->isArray(); + if ($leftIsArray->yes() && $rightIsArray->yes()) { + if ($this->getIterableKeyType()->equals($right->getIterableKeyType())) { + // to preserve BenevolentUnionType + $keyType = $this->getIterableKeyType(); + } else { + $keyTypes = []; + foreach ([ + $this->getIterableKeyType(), + $right->getIterableKeyType(), + ] as $keyType) { + $keyTypes[] = $keyType; + } + $keyType = TypeCombinator::union(...$keyTypes); + } + + $leftIterableValueType = $this->getIterableValueType(); + $arrayType = new ArrayType( + $keyType, + TypeCombinator::union($leftIterableValueType, $right->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 (!$this->hasOffsetValueType($offsetType)->yes()) { + continue; + } + + $valueType = $this->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 (!$right->hasOffsetValueType($offsetType)->yes()) { + continue; + } + + $valueType = TypeCombinator::union($leftIterableValueType, $right->getOffsetValueType($offsetType)); + $accessories[] = new HasOffsetValueType($offsetType, $valueType); + } + } + + if ($this->isIterableAtLeastOnce()->yes() || $right->isIterableAtLeastOnce()->yes()) { + $accessories[] = new NonEmptyArrayType(); + } + if ($this->isList()->yes() && $right->isList()->yes()) { + $accessories[] = new AccessoryArrayListType(); + } + + if (count($accessories) > 0) { + $arrayType = TypeCombinator::intersect($arrayType, ...$accessories); + } + + return $arrayType; + } + + return new ErrorType(); } } diff --git a/turbo-ext/src/IetrSupport.h b/turbo-ext/src/IetrSupport.h index 9fa9004a2a3..6fda1059343 100644 --- a/turbo-ext/src/IetrSupport.h +++ b/turbo-ext/src/IetrSupport.h @@ -1,6 +1,6 @@ /* - * The Type helpers shared by InitializerExprTypeResolver.cpp and - * OperatorTypeHelper.cpp: calling Type methods, constructing the native + * The Type helpers shared by InitializerExprTypeResolver.cpp, the value + * sets and OperatorTypeTrait.cpp: calling Type methods, constructing the native * types, and the PHP value arithmetic of the twins' operators on the * engine's own operator functions. */ diff --git a/turbo-ext/src/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp deleted file mode 100644 index 10f40077fb1..00000000000 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/* - * PHPStanTurbo\OperatorTypeHelper — native implementation of - * PHPStan\Type\OperatorTypeHelper: the array + operator (Type::arrayUnion()) - * OperatorTypeTrait delegates to (pt_type_trait_operator() in TypeTraits.cpp - * calls pt_operator_type_helper_binary() directly). The other operators are - * computed on ValueSet, NumberSet and StringSet. - * - * A final class of static methods: every method mirrors its twin, the - * private ones are registered too so the method sets stay identical. - */ - -#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: - - /* {{{ arrayUnion() */ - - /* 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(); - } - - /* }}} */ - -}; - -} // namespace phpstanturbo - -using phpstanturbo::OperatorTypeHelper; - -/* {{{ direct entries */ - -zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion) -{ - switch (op) { - case PT_OPERATOR_ARRAY_UNION: - return OperatorTypeHelper::arrayUnion(left, right); - } - pt_throw_should_not_happen(); - return zv::Val(); -} - -/* }}} */ - -/* {{{ 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(); false = - * the ValueError of an operator the twin does not handle */ -/* 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); - ptdecl::OperatorTypeHelper::declareProperties(cls); - - cls.method(sigs::arrayUnion, OT_BINARY_GLUE(OperatorTypeHelper::arrayUnion(leftType, rightType))); - cls.method(sigs::tryArrayUnion, OT_TYPES_GLUE(OperatorTypeHelper::tryArrayUnion(leftType, rightType))); - - 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/OperatorTypeTrait.cpp b/turbo-ext/src/OperatorTypeTrait.cpp new file mode 100644 index 00000000000..fa7aabd6bca --- /dev/null +++ b/turbo-ext/src/OperatorTypeTrait.cpp @@ -0,0 +1,208 @@ +/* + * The native body of OperatorTypeTrait::arrayUnion(), the array + operator + * (src/Type/Traits/OperatorTypeTrait.php): pt_type_trait_operator() in + * TypeTraits.cpp registers it. The other operators of the trait hand their + * operands' ValueSets over (ValueSet.cpp, NumberSet.cpp and StringSet.cpp). + */ + +#include "support.h" + +#include "zv.h" +#include "IetrSupport.h" + +using namespace phpstanturbo::ietr; + +/* Mirrors OperatorTypeTrait::arrayUnion() of $leftType: $this, $rightType: $right */ +zv::Val pt_operator_type_trait_array_union(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 newErrorType(); +} diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index 9ad77aef64a..e6f5d7444e4 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -5480,15 +5480,6 @@ 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)); -} - /* $this->toValueSet()->($right->toValueSet(), $phpVersion) */ static void operatorValueSet(INTERNAL_FUNCTION_PARAMETERS, const char *lcOperator, size_t len) { @@ -5621,7 +5612,12 @@ void pt_type_trait_operator(reg::Class &cls) cls.traitMethod(sigs::bitwiseShiftLeft, PT_OPERATOR_VALUE_SET("bitwiseshiftleft")); cls.traitMethod(sigs::bitwiseShiftRight, PT_OPERATOR_VALUE_SET("bitwiseshiftright")); cls.traitMethod(sigs::stringConcatenate, PT_OPERATOR_VALUE_SET("stringconcatenate")); - cls.traitMethod(sigs::arrayUnion, operatorBinary); + /* the array + operator, its body is in OperatorTypeTrait.cpp */ + cls.traitMethod(sigs::arrayUnion, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *right, *phpVersion; + if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); + PT_RETURN_VAL(pt_operator_type_trait_array_union(ZEND_THIS, right)); + }); /* $this->toValueSet()->getPossiblyLooselyEqualType($phpVersion) */ cls.traitMethod(sigs::getPossiblyLooselyEqualType, [](INTERNAL_FUNCTION_PARAMETERS) { zval *phpVersion; diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 5e84c498cf9..c03e8b1d717 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -1119,9 +1119,9 @@ inline constexpr reg::Arg templateTypeVariance = reg::obj("", ptcls::templateTyp } // 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. */ + * handing the operands' ValueSets over (ValueSet.cpp), arrayUnion() + * (OperatorTypeTrait.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 diff --git a/turbo-ext/src/generated/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h deleted file mode 100644 index 2f0cb94e7ba..00000000000 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ /dev/null @@ -1,53 +0,0 @@ -/* 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[] = - "leftType\0" /* 0 */ - "PHPStan\\Type\\Type\0" /* 9 */ - "rightType\0" /* 27 */ - "phpVersion\0" /* 37 */ - "PHPStan\\Php\\PhpVersion\0" /* 48 */ - "\0" /* 71 */ - "arrayUnion\0" /* 72 */ - "tryArrayUnion"; /* 83 */ -inline constexpr reg::PackedArg args[] = { - reg::packed(0, 0, 9), /* arrayUnion $leftType */ - reg::packed(27, 0, 9), /* arrayUnion $rightType */ - reg::packed(37, 0, 48), /* arrayUnion $phpVersion */ - reg::packed(71, 0, 9), /* arrayUnion return */ - reg::packed(0, 0, 9), /* tryArrayUnion $leftType */ - reg::packed(27, 0, 9), /* tryArrayUnion $rightType */ - reg::packed(71, MAY_BE_NULL, 9), /* tryArrayUnion 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 arrayUnion = { { 72 /* arrayUnion */, 3, 0, 3, 3, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 83 /* tryArrayUnion */, 2, 4, 2, 6, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -} // namespace sig - -} // namespace ptdecl::OperatorTypeHelper - -#endif diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 03644f18991..a6014ef1d82 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -4089,22 +4089,15 @@ 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) */ +/* {{{ OperatorTypeTrait.cpp — the native body of OperatorTypeTrait::arrayUnion() */ -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 operators OperatorTypeTrait still hands to OperatorTypeHelper */ -enum pt_operator_type_op : uint8_t -{ - PT_OPERATOR_ARRAY_UNION, -}; -/* OperatorTypeHelper::arrayUnion($left, $right, $phpVersion) - * (all borrowed); UNDEF = pending exception */ -zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion); +/* $left->arrayUnion($right, $phpVersion) of OperatorTypeTrait (both + * borrowed); UNDEF = pending exception */ +zv::Val pt_operator_type_trait_array_union(zval *left, zval *right); /* }}} */ diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index c632b6d995d..30c7f71d7db 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -1119,7 +1119,6 @@ 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; $covered[\PHPStan\Type\ValueSet::class] = true; $covered[\PHPStan\Type\StringSet::class] = true; // OperatorTypeTrait::exponentiate() hands arithmeticPower() the newest PhpVersion; the @@ -1194,7 +1193,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\FloatRangeType::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\Type\ValueSet::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, \PHPStan\Type\StringSet::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\FloatRangeType::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\ValueSet::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, \PHPStan\Type\StringSet::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 5a23bc8c8da..4547a6fbe7f 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -7791,7 +7791,6 @@ 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, 'arrayUnion'))->isInternal(); $observations['native PHPStan\Type\StringSet'] = (new ReflectionMethod(\PHPStan\Type\StringSet::class, 'concatenate'))->isInternal(); $observations['native PHPStan\Type\ValueSet'] = (new ReflectionMethod(\PHPStan\Type\ValueSet::class, 'union'))->isInternal(); { From f5c1630d2741fa7ff22f7b02577e7042daf6e49d Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 07:38:39 +0000 Subject: [PATCH 08/15] Compare scalar values on ValueSet and remove LooseComparisonHelper PhpVersions::getLooseComparisonVersions() lists the versions whose loose comparison semantics differ; numeric string parsing moves to StringSet. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- .../Helper/IdenticalNarrowingHelper.php | 21 +- src/Php/PhpVersions.php | 36 ++ .../InitializerExprTypeResolver.php | 44 +- src/Rules/Comparison/SwitchConditionRule.php | 11 +- src/Type/LooseComparisonHelper.php | 288 ----------- src/Type/NullType.php | 2 +- ...InArrayFunctionTypeSpecifyingExtension.php | 19 +- src/Type/StringSet.php | 64 ++- src/Type/Traits/ConstantScalarTypeTrait.php | 8 +- src/Type/ValueSet.php | 112 ++++- turbo-ext/src/IdenticalNarrowingHelper.cpp | 41 +- turbo-ext/src/InitializerExprTypeResolver.cpp | 56 ++- turbo-ext/src/NullType.cpp | 16 +- turbo-ext/src/StringSet.cpp | 151 +++++- turbo-ext/src/TypeTraits.cpp | 23 +- turbo-ext/src/TypeTraits.h | 8 + turbo-ext/src/ValueSet.cpp | 181 ++++++- turbo-ext/src/generated/StringSet.h | 22 +- turbo-ext/src/generated/ValueSet.h | 458 +++++++++--------- turbo-ext/src/support.cpp | 1 - turbo-ext/src/support.h | 1 - turbo-ext/tests/type-family.php | 21 +- 22 files changed, 957 insertions(+), 627 deletions(-) delete mode 100644 src/Type/LooseComparisonHelper.php diff --git a/src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php b/src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php index 2d974c9877d..52f49b047e7 100644 --- a/src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php +++ b/src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php @@ -36,7 +36,6 @@ 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\NullType; @@ -640,7 +639,7 @@ public function specifyEqual( // each side is one of the values the other side can equal, and in // the false branch none of the values the other side always equals - $phpVersions = LooseComparisonHelper::getRepresentativePhpVersions($evaluationScope->getPhpVersion()); + $phpVersions = $evaluationScope->getPhpVersion()->getLooseComparisonVersions(); return $this->createLooselyEqualTypes($evaluationScope, $left, $leftResult, $leftType, $rightType, $phpVersions, $context) ->unionWith($this->createLooselyEqualTypes($evaluationScope, $right, $rightResult, $rightType, $leftType, $phpVersions, $context)); @@ -651,14 +650,24 @@ public function specifyEqual( */ private function createLooselyEqualTypes(MutatingScope $evaluationScope, Expr $subject, ExpressionResult $subjectResult, Type $subjectType, Type $otherType, array $phpVersions, TypeSpecifierContext $context): SpecifiedTypes { - $type = $context->true() - ? LooseComparisonHelper::getPossiblyLooselyEqualTypeInVersions($otherType, $phpVersions) - : LooseComparisonHelper::getAlwaysLooselyEqualTypeInVersions($otherType, $phpVersions); + // in the true branch any of the versions' values, in the false branch those of every version + $types = []; + foreach ($phpVersions as $phpVersion) { + $types[] = $context->true() ? $otherType->getPossiblyLooselyEqualType($phpVersion) : $otherType->getAlwaysLooselyEqualType($phpVersion); + } + $type = $context->true() ? TypeCombinator::union(...$types) : TypeCombinator::intersect(...$types); // nothing to intersect with, or nothing to remove if ($type->isSuperTypeOf(new MixedType())->yes() || ($context->false() && $type instanceof NeverType)) { return new SpecifiedTypes([], []); } - if ($context->true() && $subjectType instanceof MixedType && LooseComparisonHelper::keepsMixedUnnarrowed($type)) { + // a mixed subject stays mixed rather than gain objects of classes like Stringable or GMP, + // which say nothing about the value; enum cases are precise enough to narrow to + if ( + $context->true() + && $subjectType instanceof MixedType + && !$type->isObject()->no() + && !TypeCombinator::intersect($type, new ObjectWithoutClassType())->isEnum()->yes() + ) { return new SpecifiedTypes([], []); } diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 84bd2c3f5f2..4c4251e21ec 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -2,10 +2,13 @@ namespace PHPStan\Php; +use PHPStan\Analyser\ConstantResolver; use PHPStan\TrinaryLogic; use PHPStan\Turbo\ReferencedByTurboExtension; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\Type; +use function count; +use function is_int; /** * Range-aware PHP version check that handles version uncertainty. @@ -22,6 +25,9 @@ final class PhpVersions { + // PHP 8.0 compares a number with a non-numeric string as strings, PHP 8.4 adds BcMath\Number + private const LOOSE_COMPARISON_PHP_VERSION_BOUNDARIES = [80000, 80400]; + public function __construct( private Type $phpVersions, ) @@ -33,6 +39,36 @@ public function getType(): Type return $this->phpVersions; } + /** + * One PHP version for each range of the versions that loosely compares (`==`) the same way. + * + * @return non-empty-list + */ + public function getLooseComparisonVersions(): array + { + $versionIds = $this->phpVersions->getConstantScalarValues(); + if (count($versionIds) === 1 && is_int($versionIds[0])) { + return [new PhpVersion($versionIds[0])]; + } + + $mins = [ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, ...self::LOOSE_COMPARISON_PHP_VERSION_BOUNDARIES]; + $versions = []; + foreach ($mins as $i => $min) { + $segment = IntegerRangeType::fromInterval($min, isset($mins[$i + 1]) ? $mins[$i + 1] - 1 : null); + if ($segment->isSuperTypeOf($this->phpVersions)->no()) { + continue; + } + + $versions[] = new PhpVersion($min); + } + if ($versions === []) { + // no PHP version reaches this code, any of them will do + return [new PhpVersion($mins[0])]; + } + + return $versions; + } + public function supportsNoncapturingCatches(): TrinaryLogic { return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 2118c1f7d2e..582a8120b6c 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -71,7 +71,6 @@ 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; @@ -92,12 +91,14 @@ use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnaryOperatorTypeSpecifyingExtensionRegistry; use PHPStan\Type\UnionType; +use PHPStan\Type\ValueSet; use stdClass; use Throwable; use function array_key_exists; use function array_keys; use function array_map; use function array_merge; +use function array_slice; use function array_values; use function count; use function dirname; @@ -997,7 +998,7 @@ public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallb $resultTypes = []; foreach ($leftValues as $leftValue) { foreach ($rightValues as $rightValue) { - $resultType = new ConstantIntegerType(LooseComparisonHelper::compareValues($leftValue, $rightValue, $this->phpVersion) ?? 1); + $resultType = new ConstantIntegerType(ValueSet::compareScalarValues($leftValue, $rightValue, $this->phpVersion) ?? 1); $resultTypes[] = $resultType; } } @@ -1182,24 +1183,39 @@ public function resolveEqualType(Type $leftType, Type $rightType, ?PhpVersions $ return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveEqualType($leftValueType, $rightValueType, $phpVersions)); } - $versions = $phpVersions !== null ? LooseComparisonHelper::getRepresentativePhpVersions($phpVersions) : [$this->phpVersion]; - $result = LooseComparisonHelper::looseCompareInVersions($leftType, $rightType, $versions); + // constant only when every PHP version agrees + $versions = $phpVersions !== null ? $phpVersions->getLooseComparisonVersions() : [$this->phpVersion]; + $result = $leftType->looseCompare($rightType, $versions[0]); + foreach (array_slice($versions, 1) as $version) { + if (!$result->equals($leftType->looseCompare($rightType, $version))) { + $result = new BooleanType(); + break; + } + } if ($result->isTrue()->yes() || $result->isFalse()->yes()) { return new TypeResult($result, []); } - if ( - LooseComparisonHelper::getPossiblyLooselyEqualTypeInVersions($leftType, $versions)->isSuperTypeOf($rightType)->no() - || LooseComparisonHelper::getPossiblyLooselyEqualTypeInVersions($rightType, $versions)->isSuperTypeOf($leftType)->no() - ) { - return new TypeResult(new ConstantBooleanType(false), []); + // either side's possibly loosely equal type in any version rules the other side out + foreach ([[$leftType, $rightType], [$rightType, $leftType]] as [$type, $otherType]) { + $possiblyEqualTypes = []; + foreach ($versions as $version) { + $possiblyEqualTypes[] = $type->getPossiblyLooselyEqualType($version); + } + if (TypeCombinator::union(...$possiblyEqualTypes)->isSuperTypeOf($otherType)->no()) { + return new TypeResult(new ConstantBooleanType(false), []); + } } - if ( - LooseComparisonHelper::getAlwaysLooselyEqualTypeInVersions($leftType, $versions)->isSuperTypeOf($rightType)->yes() - || LooseComparisonHelper::getAlwaysLooselyEqualTypeInVersions($rightType, $versions)->isSuperTypeOf($leftType)->yes() - ) { - return new TypeResult(new ConstantBooleanType(true), []); + // either side's always loosely equal type in every version holds the other side + foreach ([[$leftType, $rightType], [$rightType, $leftType]] as [$type, $otherType]) { + $alwaysEqualTypes = []; + foreach ($versions as $version) { + $alwaysEqualTypes[] = $type->getAlwaysLooselyEqualType($version); + } + if (TypeCombinator::intersect(...$alwaysEqualTypes)->isSuperTypeOf($otherType)->yes()) { + return new TypeResult(new ConstantBooleanType(true), []); + } } return new TypeResult($result, []); diff --git a/src/Rules/Comparison/SwitchConditionRule.php b/src/Rules/Comparison/SwitchConditionRule.php index 0f66f11ca9b..5983b28b9f5 100644 --- a/src/Rules/Comparison/SwitchConditionRule.php +++ b/src/Rules/Comparison/SwitchConditionRule.php @@ -15,7 +15,6 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function count; @@ -53,7 +52,7 @@ public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataE $nextCaseIsDeadForType = false; $nextCaseIsDeadForNativeType = false; $seenCases = []; - $phpVersions = LooseComparisonHelper::getRepresentativePhpVersions($scope->getPhpVersion()); + $phpVersions = $scope->getPhpVersion()->getLooseComparisonVersions(); foreach ($node->getArms() as $arm) { if ( @@ -182,7 +181,13 @@ private function isDuplicateCase(Type $seenType, Type $caseValueType, array $php return true; } - return LooseComparisonHelper::looseCompareInVersions($seenType, $caseValueType, $phpVersions)->isTrue()->yes(); + foreach ($phpVersions as $phpVersion) { + if (!$seenType->looseCompare($caseValueType, $phpVersion)->isTrue()->yes()) { + return false; + } + } + + return true; } } diff --git a/src/Type/LooseComparisonHelper.php b/src/Type/LooseComparisonHelper.php deleted file mode 100644 index e5a70c12207..00000000000 --- a/src/Type/LooseComparisonHelper.php +++ /dev/null @@ -1,288 +0,0 @@ -`) the way - * the configured PHP version does it, independently of the PHP version PHPStan runs on. - */ -#[ReferencedByTurboExtension(key: 'looseComparisonHelper')] -final class LooseComparisonHelper -{ - - private const LOOSE_COMPARISON_PHP_VERSION_BOUNDARIES = [80000, 80400]; - - 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+)?)'; - - /** - * Whether a mixed subject that loosely equals a value of $possiblyLooselyEqualType - * should stay mixed: narrowing it would add objects of classes like Stringable or GMP - * that say nothing about the value. Enum cases are precise enough to narrow to. - */ - public static function keepsMixedUnnarrowed(Type $possiblyLooselyEqualType): bool - { - if ($possiblyLooselyEqualType->isObject()->no()) { - return false; - } - - return !TypeCombinator::intersect($possiblyLooselyEqualType, new ObjectWithoutClassType())->isEnum()->yes(); - } - - /** - * One PHP version for each range of $phpVersions that loosely compares the same way. - * The rules change in PHP 8.0 (string to number comparison) and 8.4 (BcMath\Number). - * - * @return non-empty-list - */ - public static function getRepresentativePhpVersions(PhpVersions $phpVersions): array - { - $versionType = $phpVersions->getType(); - $versionIds = $versionType->getConstantScalarValues(); - if (count($versionIds) === 1 && is_int($versionIds[0])) { - return [new PhpVersion($versionIds[0])]; - } - - $mins = [ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, ...self::LOOSE_COMPARISON_PHP_VERSION_BOUNDARIES]; - $versions = []; - foreach ($mins as $i => $min) { - $segment = IntegerRangeType::fromInterval($min, isset($mins[$i + 1]) ? $mins[$i + 1] - 1 : null); - if ($segment->isSuperTypeOf($versionType)->no()) { - continue; - } - - $versions[] = new PhpVersion($min); - } - if ($versions === []) { - // no PHP version reaches this code, any of them will do - return [new PhpVersion($mins[0])]; - } - - return $versions; - } - - /** - * The values $type can loosely equal on any of $phpVersions. - * - * @param non-empty-list $phpVersions - */ - public static function getPossiblyLooselyEqualTypeInVersions(Type $type, array $phpVersions): Type - { - $types = []; - foreach ($phpVersions as $phpVersion) { - $types[] = $type->getPossiblyLooselyEqualType($phpVersion); - } - - return TypeCombinator::union(...$types); - } - - /** - * The values $type loosely equals on every one of $phpVersions. - * - * @param non-empty-list $phpVersions - */ - public static function getAlwaysLooselyEqualTypeInVersions(Type $type, array $phpVersions): Type - { - $types = []; - foreach ($phpVersions as $phpVersion) { - $types[] = $type->getAlwaysLooselyEqualType($phpVersion); - } - - return TypeCombinator::intersect(...$types); - } - - /** - * `$leftType == $rightType` on $phpVersions, constant only when they all agree. - * - * @param non-empty-list $phpVersions - */ - public static function looseCompareInVersions(Type $leftType, Type $rightType, array $phpVersions): BooleanType - { - $result = $leftType->looseCompare($rightType, $phpVersions[0]); - foreach (array_slice($phpVersions, 1) as $phpVersion) { - if (!$result->equals($leftType->looseCompare($rightType, $phpVersion))) { - return new BooleanType(); - } - } - - return $result; - } - - public static function compareConstantScalars(ConstantScalarType $leftType, ConstantScalarType $rightType, PhpVersion $phpVersion): BooleanType - { - return new ConstantBooleanType(self::compareValues($leftType->getValue(), $rightType->getValue(), $phpVersion) === 0); - } - - /** - * 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); - } - - $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; - } - - return $left <=> $right; - } - - /** - * Mirrors zendi_smart_strcmp(): numeric strings compare as numbers, any other pair byte by byte. - */ - 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 - */ - public 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. - */ - public 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/NullType.php b/src/Type/NullType.php index ec3dd736274..5feb9e98479 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -362,7 +362,7 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { if ($type instanceof ConstantScalarType) { - return LooseComparisonHelper::compareConstantScalars($this, $type, $phpVersion); + return new ConstantBooleanType(ValueSet::compareScalarValues($this->getValue(), $type->getValue(), $phpVersion) === 0); } if ($type->isConstantArray()->yes() && $type->isIterableAtLeastOnce()->no()) { diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 83f31464b61..d27bf39017a 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -18,8 +18,8 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function count; @@ -116,13 +116,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $specifiedTypes = new SpecifiedTypes(); if ($context->true()) { // the needle is one of the values an item can loosely equal - $possiblyEqualType = LooseComparisonHelper::getPossiblyLooselyEqualTypeInVersions( - $arrayValueType, - LooseComparisonHelper::getRepresentativePhpVersions($scope->getPhpVersion()), - ); + $possiblyEqualTypes = []; + foreach ($scope->getPhpVersion()->getLooseComparisonVersions() as $phpVersion) { + $possiblyEqualTypes[] = $arrayValueType->getPossiblyLooselyEqualType($phpVersion); + } + $possiblyEqualType = TypeCombinator::union(...$possiblyEqualTypes); if ( !$possiblyEqualType->isSuperTypeOf(new MixedType())->yes() - && (!$needleType instanceof MixedType || !LooseComparisonHelper::keepsMixedUnnarrowed($possiblyEqualType)) + // a mixed needle stays mixed rather than gain objects of classes like Stringable or GMP, + // which say nothing about the value; enum cases are precise enough to narrow to + && ( + !$needleType instanceof MixedType + || $possiblyEqualType->isObject()->no() + || TypeCombinator::intersect($possiblyEqualType, new ObjectWithoutClassType())->isEnum()->yes() + ) ) { $specifiedTypes = $this->typeSpecifier->create($needleExpr, $possiblyEqualType, $context, $scope); } diff --git a/src/Type/StringSet.php b/src/Type/StringSet.php index 272a93f27fb..4e03cec114c 100644 --- a/src/Type/StringSet.php +++ b/src/Type/StringSet.php @@ -5,6 +5,7 @@ use Nette\Utils\Strings; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -15,7 +16,9 @@ use PHPStan\Type\Constant\ConstantStringType; use function count; use function in_array; +use function is_float; use function is_numeric; +use function preg_match; use function rtrim; use function strtolower; use function strtoupper; @@ -29,8 +32,11 @@ final class StringSet { - // the characters PCRE's \s matches, which PHP 8 allows after a numeric string - private const WHITESPACE = " \t\n\v\f\r"; + // the whitespace PHP allows before a numeric string, and since PHP 8.0 after it, the characters PCRE's \s matches + 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+)?)'; /** * @param list $constants @@ -337,15 +343,57 @@ private function isNumberSuffix(bool $afterInteger, PhpVersion $phpVersion): boo return $this->type === null || $this->digits; } - /** is_numeric() of the PHP version, which PHP 8 extends to trailing whitespace. */ - private static function isNumeric(string $value, PhpVersion $phpVersion): bool + /** + * 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 + */ + public 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. */ + public 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 { - $trimmed = rtrim($value, self::WHITESPACE); - if ($trimmed !== $value && !$phpVersion->allowsTrailingWhitespaceInNumericStrings()) { - return false; + $numericString = $matches[1]; + if (!is_numeric($numericString)) { + throw new ShouldNotHappenException(); } - return is_numeric($trimmed); + $number = +$numericString; + $isIntegerSyntax = ($matches[2] ?? '') !== '' && ($matches[3] ?? '') === ''; + if ($isIntegerSyntax && is_float($number)) { + return [$number, $number < 0 ? -1 : 1]; + } + + return [$number, 0]; + } + + private static function isNumeric(string $value, PhpVersion $phpVersion): bool + { + return self::parseNumericString($value, $phpVersion) !== null; } /** @param list $constants */ diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 23509fde477..db1f71db5e5 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -11,8 +11,8 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\IsSuperTypeOfResult; -use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\Type; +use PHPStan\Type\ValueSet; trait ConstantScalarTypeTrait { @@ -54,7 +54,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType } if ($type instanceof ConstantScalarType) { - return LooseComparisonHelper::compareConstantScalars($this, $type, $phpVersion); + return new ConstantBooleanType(ValueSet::compareScalarValues($this->getValue(), $type->getValue(), $phpVersion) === 0); } if ($type->isConstantArray()->yes() && $type->isIterableAtLeastOnce()->no()) { @@ -77,7 +77,7 @@ public function equals(Type $type): bool public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { - $comparison = LooseComparisonHelper::compareValues($this->value, $otherType->getValue(), $phpVersion); + $comparison = ValueSet::compareScalarValues($this->value, $otherType->getValue(), $phpVersion); return TrinaryLogic::createFromBoolean($comparison !== null && $comparison < 0); } @@ -91,7 +91,7 @@ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryL public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { - $comparison = LooseComparisonHelper::compareValues($this->value, $otherType->getValue(), $phpVersion); + $comparison = ValueSet::compareScalarValues($this->value, $otherType->getValue(), $phpVersion); return TrinaryLogic::createFromBoolean($comparison !== null && $comparison <= 0); } diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php index 1557a35b21c..d8b59404096 100644 --- a/src/Type/ValueSet.php +++ b/src/Type/ValueSet.php @@ -27,6 +27,7 @@ use function is_string; use function pow; use function sprintf; +use function strcmp; use const INF; use const NAN; use const PHP_INT_MAX; @@ -336,6 +337,107 @@ public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type return $alwaysEqualType ?? new NeverType(); } + /** + * Mirrors zend_compare() of two scalar values, the way the PHP version compares them + * independently of the PHP version PHPStan runs on: -1, 0 or 1, or null when they 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 compareScalarValues(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); + } + + $result = self::compareScalarValues($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(StringSet::parseNumericPrefix($left) ?? 0, $right); + } + + $number = StringSet::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; + } + + return $left <=> $right; + } + + /** Mirrors zendi_smart_strcmp(): numeric strings compare as numbers, any other pair byte by byte. */ + private static function compareStrings(string $left, string $right, PhpVersion $phpVersion): int + { + $leftNumber = StringSet::parseNumericString($left, $phpVersion); + $rightNumber = StringSet::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; + } + /** * Models the + operator on all but two arrays, which OperatorTypeTrait::arithmeticAdd() * unions. An array and a value that is not one cannot be added. @@ -1577,7 +1679,7 @@ private static function getTypeLooselyEqualTo(bool|float|int|string|null $value, $strings[] = $value; } foreach ($strings as $string) { - if (LooseComparisonHelper::compareValues($value, $string, $phpVersion) !== 0) { + if (self::compareScalarValues($value, $string, $phpVersion) !== 0) { continue; } @@ -1603,13 +1705,13 @@ private static function getLooselyEqualNumber(bool|float|int|string|null $value, return $value; } - $number = LooseComparisonHelper::parseNumericString($value, $phpVersion); + $number = StringSet::parseNumericString($value, $phpVersion); if ($number !== null) { return $number[0]; } if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { // the number the string starts with - return LooseComparisonHelper::parseNumericPrefix($value) ?? 0; + return StringSet::parseNumericPrefix($value) ?? 0; } // a number compares with a non-numeric string as its own string @@ -1750,7 +1852,7 @@ private static function getConstantStringPartnerType(string $value, PhpVersion $ self::getTypeLooselyEqualTo($value, $phpVersion), ]; - $number = LooseComparisonHelper::parseNumericString($value, $phpVersion); + $number = StringSet::parseNumericString($value, $phpVersion); if ($number !== null) { $number = $number[0]; $types[] = self::createNumericStringType(); @@ -1759,7 +1861,7 @@ private static function getConstantStringPartnerType(string $value, PhpVersion $ $types[] = new ResourceType(); } } elseif (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { - $number = LooseComparisonHelper::parseNumericPrefix($value) ?? 0; + $number = StringSet::parseNumericPrefix($value) ?? 0; $types[] = self::createNumberType($number); if ($number >= 1 && floor($number) === (float) $number) { $types[] = new ResourceType(); diff --git a/turbo-ext/src/IdenticalNarrowingHelper.cpp b/turbo-ext/src/IdenticalNarrowingHelper.cpp index 2ce32db755c..a72e3d2f8bb 100644 --- a/turbo-ext/src/IdenticalNarrowingHelper.cpp +++ b/turbo-ext/src/IdenticalNarrowingHelper.cpp @@ -632,7 +632,7 @@ class IdenticalNarrowingHelper // the false branch none of the values the other side always equals zv::Val scopePhpVersions = pt_type_call(Z_OBJ_P(evaluationScope), PT_LC("getphpversion"), 0, NULL); if (UNEXPECTED(scopePhpVersions.isUndef())) return zv::Val(); - zv::Val phpVersions = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getrepresentativephpversions"), 1, scopePhpVersions.raw()); + zv::Val phpVersions = pt_type_call(Z_OBJ_P(scopePhpVersions.raw()), PT_LC("getloosecomparisonversions"), 0, NULL); if (UNEXPECTED(phpVersions.isUndef())) return zv::Val(); zv::Val leftTypes = createLooselyEqualTypes(evaluationScope, left, leftResult, leftType.raw(), rightType.raw(), phpVersions.raw(), context); if (UNEXPECTED(leftTypes.isUndef())) return zv::Val(); @@ -646,10 +646,23 @@ class IdenticalNarrowingHelper { bool isTrue; if (UNEXPECTED(!ctxTrue(context, isTrue))) return zv::Val(); - zv::Args args{otherType, phpVersions}; + // in the true branch any of the versions' values, in the false branch those of every version + if (UNEXPECTED(Z_TYPE_P(phpVersions) != IS_ARRAY)) { + pt_throw_should_not_happen(); + return zv::Val(); + } + uint32_t versionCount = zend_hash_num_elements(Z_ARRVAL_P(phpVersions)); + zv::Arr types = zv::Arr::create(versionCount); + for (zv::ArrayEntry entry : zv::ArrRef(phpVersions)) { + zv::Val versionType = isTrue + ? pt_type_call(Z_OBJ_P(otherType), PT_LC("getpossiblylooselyequaltype"), 1, entry.value().raw()) + : pt_type_call(Z_OBJ_P(otherType), PT_LC("getalwayslooselyequaltype"), 1, entry.value().raw()); + if (UNEXPECTED(versionType.isUndef())) return zv::Val(); + types.push(std::move(versionType)); + } zv::Val type = isTrue - ? pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getpossiblylooselyequaltypeinversions"), 2, args) - : pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getalwayslooselyequaltypeinversions"), 2, args); + ? pt_type_combinator_union(versionCount, types.table()->arPacked) + : pt_type_combinator_intersect(versionCount, types.table()->arPacked); if (UNEXPECTED(type.isUndef())) return zv::Val(); // nothing to intersect with, or nothing to remove @@ -661,10 +674,24 @@ class IdenticalNarrowingHelper bool isFalse; if (UNEXPECTED(!ctxFalse(context, isFalse))) return zv::Val(); if (isFalse && instanceof_function(Z_OBJCE_P(type.raw()), pt_ce_never_type)) return pt_specified_types_new(); + // a mixed subject stays mixed rather than gain objects of classes like Stringable or GMP, + // which say nothing about the value; enum cases are precise enough to narrow to if (isTrue && Z_TYPE_P(subjectType) == IS_OBJECT && instanceof_function(Z_OBJCE_P(subjectType), pt_ce_mixed_type)) { - zv::Val keepsMixed = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("keepsmixedunnarrowed"), 1, type.raw()); - if (UNEXPECTED(keepsMixed.isUndef())) return zv::Val(); - if (Z_TYPE_P(keepsMixed.raw()) == IS_TRUE) return pt_specified_types_new(); + zend_long isObject = trinaryByName(type.raw(), "isObject", PT_LC("isobject")); + if (UNEXPECTED(isObject < 0)) return zv::Val(); + if (isObject != PT_TRI_NO) { + zval object; + if (UNEXPECTED(!pt_object_without_class_type_new(&object))) return zv::Val(); + zval operands[2]; + ZVAL_COPY_VALUE(&operands[0], type.raw()); + ZVAL_COPY_VALUE(&operands[1], &object); + zv::Val objects = pt_type_combinator_intersect(2, operands); + zval_ptr_dtor(&object); + if (UNEXPECTED(objects.isUndef())) return zv::Val(); + zend_long isEnum = trinaryByName(objects.raw(), "isEnum", PT_LC("isenum")); + if (UNEXPECTED(isEnum < 0)) return zv::Val(); + if (isEnum != PT_TRI_YES) return pt_specified_types_new(); + } } return createSubjectTypes(evaluationScope, subject, subjectResult, type.raw(), context); diff --git a/turbo-ext/src/InitializerExprTypeResolver.cpp b/turbo-ext/src/InitializerExprTypeResolver.cpp index 9077960d4cb..7afc6b74814 100644 --- a/turbo-ext/src/InitializerExprTypeResolver.cpp +++ b/turbo-ext/src/InitializerExprTypeResolver.cpp @@ -685,12 +685,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(); + /* ValueSet::compareScalarValues($leftValue, $rightValue, $this->phpVersion) ?? 1 */ + zend_long comparisonValue; + int compared = pt_value_set_compare_scalar_values(leftEntry.value().raw(), rightEntry.value().raw(), prop(slots::phpVersion), comparisonValue); + if (UNEXPECTED(compared < 0)) return zv::Val(); zval comparison; - ZVAL_LONG(&comparison, Z_TYPE_P(compared.raw()) == IS_LONG ? Z_LVAL_P(compared.raw()) : 1); + ZVAL_LONG(&comparison, compared == 1 ? comparisonValue : 1); IETR_VAL(resultType, typeFromValue(&comparison)); resultTypes.push(std::move(resultType)); } @@ -791,12 +791,12 @@ class InitializerExprTypeResolver return resolveConstantArrayTypeComparison(leftType, rightType, false, NULL, phpVersions); } - // LooseComparisonHelper::get*InVersions() over a single version is - // that version's Type method call + // $phpVersions->getLooseComparisonVersions(), or the resolver's own + // version; the Type methods over a single version need no union zval *version = prop(slots::phpVersion); zv::Val versions; if (phpVersions != NULL) { - IETR_VAL(representative, pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getrepresentativephpversions"), 1, phpVersions)); + IETR_VAL(representative, pt_type_call(Z_OBJ_P(phpVersions), PT_LC("getloosecomparisonversions"), 0, NULL)); if (UNEXPECTED(!requireArray(representative.raw()))) return zv::Val(); if (zend_hash_num_elements(Z_ARRVAL_P(representative.raw())) == 1) { version = arrayIndexForRead(representative.raw(), 0); @@ -805,24 +805,42 @@ class InitializerExprTypeResolver versions = std::move(representative); } bool singleVersion = versions.isUndef() || zend_hash_num_elements(Z_ARRVAL_P(versions.raw())) == 1; - auto possiblyEqual = [&](zval *type) -> zv::Val { - if (singleVersion) return callOn(type, mGetPossiblyLooselyEqualType, 1, version); - zv::Args args{type, versions.raw()}; - return pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getpossiblylooselyequaltypeinversions"), 2, args); - }; - auto alwaysEqual = [&](zval *type) -> zv::Val { - if (singleVersion) return callOn(type, mGetAlwaysLooselyEqualType, 1, version); - zv::Args args{type, versions.raw()}; - return pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("getalwayslooselyequaltypeinversions"), 2, args); + /* TypeCombinator::union() / intersect() of $type->($version) over the versions */ + auto overVersions = [&](zval *type, const Method &method, bool intersect) -> zv::Val { + if (singleVersion) return callOn(type, method, 1, version); + uint32_t count = zend_hash_num_elements(Z_ARRVAL_P(versions.raw())); + zv::Arr types = zv::Arr::create(count); + for (zv::ArrayEntry entry : zv::ArrRef(versions.raw())) { + IETR_VAL(versionType, callOn(type, method, 1, entry.value().raw())); + types.push(std::move(versionType)); + } + if (intersect) return pt_type_combinator_intersect(count, types.table()->arPacked); + return unionOf(types); }; + auto possiblyEqual = [&](zval *type) -> zv::Val { return overVersions(type, mGetPossiblyLooselyEqualType, false); }; + auto alwaysEqual = [&](zval *type) -> zv::Val { return overVersions(type, mGetAlwaysLooselyEqualType, true); }; + // constant only when every PHP version agrees zv::Val compared; if (singleVersion) { zv::Args argv{rightType, version}; compared = callOn(leftType, mLooseCompare, 2, argv); } else { - zv::Args argv{leftType, rightType, versions.raw()}; - compared = pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("loosecompareinversions"), 3, argv); + bool first = true; + for (zv::ArrayEntry entry : zv::ArrRef(versions.raw())) { + zv::Args argv{rightType, entry.value().raw()}; + IETR_VAL(versionResult, callOn(leftType, mLooseCompare, 2, argv)); + if (first) { + compared = std::move(versionResult); + first = false; + continue; + } + IETR_VAL(equals, callOn(compared.raw(), mEquals, 1, versionResult.raw())); + if (!zend_is_true(equals.raw())) { + compared = newBooleanType(); + break; + } + } } IETR_VAL(result, std::move(compared)); IETR_TRI(resultIsTrue, triOn(result.raw(), mIsTrue)); diff --git a/turbo-ext/src/NullType.cpp b/turbo-ext/src/NullType.cpp index 52ff69cd9dc..2c3f0e38c03 100644 --- a/turbo-ext/src/NullType.cpp +++ b/turbo-ext/src/NullType.cpp @@ -229,8 +229,8 @@ class NullType static zend_long isVoid() { return PT_TRI_NO; } static zend_long isScalar() { return PT_TRI_NO; } - /* LooseComparisonHelper::compareConstantScalars($this, $type, $phpVersion) - * for a ConstantScalarType, new ConstantBooleanType($this->getValue() == []) + /* new ConstantBooleanType(ValueSet::compareScalarValues($this->getValue(), + * $type->getValue(), $phpVersion) === 0) for a ConstantScalarType, new ConstantBooleanType($this->getValue() == []) * for an empty constant array, the CompoundType callback, new * BooleanType() otherwise; UNDEF = pending exception */ zv::Val looseCompare(zval *type, zval *phpVersion) const @@ -238,8 +238,16 @@ class NullType bool isConstantScalar; if (UNEXPECTED(!pt_type_instanceof(type, PT_CLASS_CONSTANT_SCALAR_TYPE, isConstantScalar))) return zv::Val(); if (isConstantScalar) { - zv::Args args{self, type, phpVersion}; - return pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("compareconstantscalars"), 3, args); + zval selfValue; + ZVAL_NULL(&selfValue); + zv::Val otherValue = pt_type_call(Z_OBJ_P(type), PT_LC("getvalue"), 0, NULL); + if (UNEXPECTED(otherValue.isUndef())) return zv::Val(); + zend_long comparison; + int compared = pt_value_set_compare_scalar_values(&selfValue, otherValue.raw(), phpVersion, comparison); + if (UNEXPECTED(compared < 0)) return zv::Val(); + zval result; + if (UNEXPECTED(!pt_constant_boolean_type_new(&result, compared == 1 && comparison == 0))) return zv::Val(); + return zv::Val::adopt(result); } zend_long isConstantArray = pt_type_op_trinary(Z_OBJ_P(type), PT_OP_IS_CONSTANT_ARRAY, 0, NULL); diff --git a/turbo-ext/src/StringSet.cpp b/turbo-ext/src/StringSet.cpp index 3c05f8e91e3..91e0fb9f49c 100644 --- a/turbo-ext/src/StringSet.cpp +++ b/turbo-ext/src/StringSet.cpp @@ -53,7 +53,8 @@ class StringSetView class StringSetOps { public: - static constexpr const char *WHITESPACE = " \t\n\v\f\r"; + static constexpr const char *WHITESPACE = " \t\n\r\v\f"; + static constexpr const char *NUMBER = "([+-]?(?:\\d+\\.\\d*|\\.\\d+|(\\d+))([eE][+-]?\\d+)?)"; /* new self($constants, $type, $digits, $whitespaceFree); type UNDEF or null for none */ static zv::Val newStringSet(zv::Arr &constants, zv::Val type, bool digits, bool whitespaceFree) @@ -434,15 +435,118 @@ class StringSetOps return length; } - static int isNumeric(zend_string *value, zval *phpVersion) + static bool isWhitespace(char c) + { + return c != '\0' && strchr(WHITESPACE, c) != NULL; + } + + static bool isDigit(char c) + { + return c >= '0' && c <= '9'; + } + + /* the match of '/^[WHITESPACE]*' . NUMBER (. '[WHITESPACE]*$/D' when + * anchored, without the whitespace for PHP 7): the span of the number + * and whether it is an integer, without a fraction or an exponent */ + static bool matchNumber(zend_string *value, bool anchored, bool trailingWhitespace, size_t &start, size_t &length, bool &integerSyntax) { - size_t length = trimmedLength(value); - if (length != ZSTR_LEN(value)) { - bool allows; - if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS, allows))) return -1; - if (!allows) return 0; + const char *s = ZSTR_VAL(value); + size_t n = ZSTR_LEN(value); + size_t i = 0; + while (i < n && isWhitespace(s[i])) i++; + start = i; + if (i < n && (s[i] == '+' || s[i] == '-')) i++; + size_t integerDigits = 0; + while (i < n && isDigit(s[i])) { + i++; + integerDigits++; + } + bool fraction = false; + if (integerDigits > 0) { + if (i < n && s[i] == '.') { + i++; + while (i < n && isDigit(s[i])) i++; + fraction = true; + } + } else { + if (!(i + 1 < n && s[i] == '.' && isDigit(s[i + 1]))) return false; + i++; + while (i < n && isDigit(s[i])) i++; + fraction = true; } - return is_numeric_string(ZSTR_VAL(value), length, NULL, NULL, false) != 0 ? 1 : 0; + bool exponent = false; + if (i < n && (s[i] == 'e' || s[i] == 'E')) { + size_t j = i + 1; + if (j < n && (s[j] == '+' || s[j] == '-')) j++; + size_t exponentDigits = 0; + while (j < n && isDigit(s[j])) { + j++; + exponentDigits++; + } + if (exponentDigits > 0) { + i = j; + exponent = true; + } + } + length = i - start; + integerSyntax = integerDigits > 0 && !fraction && !exponent; + if (anchored) { + if (trailingWhitespace) { + while (i < n && isWhitespace(s[i])) i++; + } + if (i != n) return false; + } + return true; + } + + /* toNumber(): +$numericString into `number`, the overflow direction */ + static int toNumber(zend_string *value, size_t start, size_t length, bool integerSyntax, zval *number) + { + zend_long lval = 0; + double dval = 0.0; + zend_uchar type = is_numeric_string(ZSTR_VAL(value) + start, length, &lval, &dval, false); + if (UNEXPECTED(type == 0)) { + pt_throw_should_not_happen(); + return -2; + } + if (type == IS_LONG) { + ZVAL_LONG(number, lval); + return 0; + } + ZVAL_DOUBLE(number, dval); + if (integerSyntax) return dval < 0 ? -1 : 1; + return 0; + } + + /* parseNumericString(): 1 with the number and the overflow direction, 0 + * for null, -1 = pending exception */ + static int parseNumericString(zend_string *value, zval *phpVersion, zval *number, int &overflow) + { + bool trailingWhitespace; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS, trailingWhitespace))) return -1; + size_t start, length; + bool integerSyntax; + if (!matchNumber(value, true, trailingWhitespace, start, length, integerSyntax)) return 0; + int direction = toNumber(value, start, length, integerSyntax, number); + if (UNEXPECTED(direction == -2)) return -1; + overflow = direction; + return 1; + } + + /* parseNumericPrefix(): 1 with the number, 0 for null, -1 = pending exception */ + static int parseNumericPrefix(zend_string *value, zval *number) + { + size_t start, length; + bool integerSyntax; + if (!matchNumber(value, false, false, start, length, integerSyntax)) return 0; + return toNumber(value, start, length, integerSyntax, number) == -2 ? -1 : 1; + } + + static int isNumeric(zend_string *value, zval *phpVersion) + { + zval number; + int overflow; + return parseNumericString(value, phpVersion, &number, overflow); } static int contains(HashTable *constants, zend_string *value) @@ -527,6 +631,16 @@ bool pt_string_set_is_empty(zval *set) return StringSetView(Z_OBJ_P(set)).isEmpty(); } +int pt_string_set_parse_numeric_string(zend_string *value, zval *phpVersion, zval *number, int &overflow) +{ + return StringSetOps::parseNumericString(value, phpVersion, number, overflow); +} + +int pt_string_set_parse_numeric_prefix(zend_string *value, zval *number) +{ + return StringSetOps::parseNumericPrefix(value, number); +} + /* }}} */ /* {{{ engine ABI glue: parameter parsing + registration */ @@ -556,6 +670,7 @@ PT_MINIT_REGISTRATION(pt_register_string_set) reg::Class cls("PHPStan\\Type\\StringSet"); ptdecl::StringSet::declareClass(cls); cls.privateClassConstantString("WHITESPACE", StringSetOps::WHITESPACE); + cls.privateClassConstantString("NUMBER", StringSetOps::NUMBER); /* "constants", "type", "digits" and "whitespaceFree" must stay the * declared properties in this order (slots::*) */ ptdecl::StringSet::declareProperties(cls); @@ -600,6 +715,26 @@ PT_MINIT_REGISTRATION(pt_register_string_set) ZEND_PARSE_PARAMETERS_NONE(); PT_RETURN_VAL(StringSetOps::toType(PT_SELF)); }); + cls.method(sigs::parseNumericString, [](INTERNAL_FUNCTION_PARAMETERS) { + zend_string *value; + zval *phpVersion; + if (!zp::parse(execute_data, value, phpVersion)) RETURN_THROWS(); + zval number; + int overflow; + int parsed = StringSetOps::parseNumericString(value, phpVersion, &number, overflow); + if (UNEXPECTED(parsed < 0)) RETURN_THROWS(); + if (parsed == 0) RETURN_NULL(); + array_init_size(return_value, 2); + add_next_index_zval(return_value, &number); + add_next_index_long(return_value, overflow); + }); + cls.method(sigs::parseNumericPrefix, [](INTERNAL_FUNCTION_PARAMETERS) { + zend_string *value; + if (!zp::parse(execute_data, value)) RETURN_THROWS(); + int parsed = StringSetOps::parseNumericPrefix(value, return_value); + if (UNEXPECTED(parsed < 0)) RETURN_THROWS(); + if (parsed == 0) RETURN_NULL(); + }); cls.method(sigs::concatenate, [](INTERNAL_FUNCTION_PARAMETERS) { zval *right, *phpVersion; if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index e6f5d7444e4..a64629da9f3 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -1075,8 +1075,17 @@ zv::Val pt_type_constant_scalar_loose_compare(zend_object *self, zend_class_entr if (UNEXPECTED(!pt_type_instanceof(type, PT_CLASS_CONSTANT_SCALAR_TYPE, isConstantScalar))) return zv::Val(); if (isConstantScalar) { - zv::Args args{&selfZv, type, phpVersion}; - return pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, PT_LC("compareconstantscalars"), 3, args); + /* new ConstantBooleanType(ValueSet::compareScalarValues($this->getValue(), $type->getValue(), $phpVersion) === 0) */ + zv::Val selfValue = constantScalarGetValue(self); + if (UNEXPECTED(selfValue.isUndef())) return zv::Val(); + zv::Val otherValue = constantScalarGetValue(Z_OBJ_P(type)); + if (UNEXPECTED(otherValue.isUndef())) return zv::Val(); + zend_long comparison; + int compared = pt_value_set_compare_scalar_values(selfValue.raw(), otherValue.raw(), phpVersion, comparison); + if (UNEXPECTED(compared < 0)) return zv::Val(); + zval result; + if (UNEXPECTED(!pt_constant_boolean_type_new(&result, compared == 1 && comparison == 0))) return zv::Val(); + return zv::Val::adopt(result); } zend_long isConstantArray = pt_type_op_trinary(Z_OBJ_P(type), PT_OP_IS_CONSTANT_ARRAY, 0, NULL); @@ -1131,12 +1140,12 @@ 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(); - /* LooseComparisonHelper::compareValues($this->value, $otherType->getValue(), $phpVersion): + /* ValueSet::compareScalarValues($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); + zend_long comparison; + int compared = pt_value_set_compare_scalar_values(selfValue, otherValue.raw(), phpVersion, comparison); + if (UNEXPECTED(compared < 0)) RETURN_THROWS(); + bool holds = compared == 1 && (orEqual ? comparison <= 0 : comparison < 0); PT_RETURN_TRINARY(holds ? PT_TRI_YES : PT_TRI_NO); } diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index c03e8b1d717..1566804c518 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -189,6 +189,9 @@ void pt_value_set_of_members(INTERNAL_FUNCTION_PARAMETERS, bool intersect); /* $type->toValueSet()->arithmeticPower($exponent->toValueSet(), $phpVersion); * UNDEF = pending exception */ zv::Val pt_value_set_power(zend_object *type, zval *exponent, zval *phpVersion); +/* ValueSet::compareScalarValues($left, $right, $phpVersion): 1 with the + * -1 / 0 / 1 in `out`, 0 for its null (a NAN), -1 = pending exception */ +int pt_value_set_compare_scalar_values(zval *left, zval *right, zval *phpVersion, zend_long &out); #define PT_VALUE_SET_OF_THIS(factory) \ [](INTERNAL_FUNCTION_PARAMETERS) { pt_value_set_of_this(INTERNAL_FUNCTION_PARAM_PASSTHRU, PT_LC(factory)); } @@ -363,6 +366,11 @@ zv::Val pt_string_set_concatenate(zval *left, zval *right, zval *phpVersion); HashTable *pt_string_set_constants(zval *set); zval *pt_string_set_type(zval *set); bool pt_string_set_is_empty(zval *set); +/* StringSet::parseNumericString() / parseNumericPrefix(): 1 with the number + * written to `number` (and the overflow direction), 0 for their null, -1 = + * pending exception */ +int pt_string_set_parse_numeric_string(zend_string *value, zval *phpVersion, zval *number, int &overflow); +int pt_string_set_parse_numeric_prefix(zend_string *value, zval *number); /* the bodies of IntegerType::looseCompare() and IntegerType::arithmeticPower() * (IntegerType.cpp), for the children's parent:: calls; UNDEF = pending diff --git a/turbo-ext/src/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp index f18a58c64f8..8e94bd93705 100644 --- a/turbo-ext/src/ValueSet.cpp +++ b/turbo-ext/src/ValueSet.cpp @@ -8,8 +8,7 @@ * only, natively; an uninitialized slot reads as the twin's default. * * The twin's private methods live only here, in ValueSetOps, method for - * method. LooseComparisonHelper stays PHP: compareValues() and the - * numeric-string parsers are called there. + * method. The numeric-string parsers are StringSet's (StringSet.cpp). */ #include "TypeTraits.h" @@ -1752,11 +1751,121 @@ class ValueSetOps /* {{{ loose equality */ - static zv::Val looseComparisonHelper(const char *lcMethod, size_t len, uint32_t argc, zval *argv) + /* {{{ compareScalarValues(): 1 with the -1 / 0 / 1 in `out`, 0 for its + * null, -1 = pending exception */ + + static bool isNan(zval *value) + { + return Z_TYPE_P(value) == IS_DOUBLE && std::isnan(Z_DVAL_P(value)); + } + + static bool isNullOrBool(zval *value) + { + return Z_TYPE_P(value) == IS_NULL || Z_TYPE_P(value) == IS_TRUE || Z_TYPE_P(value) == IS_FALSE; + } + + static int compareScalarValues(zval *left, zval *right, zval *phpVersion, zend_long &out) + { + if (Z_TYPE_P(left) != IS_STRING) { + if (Z_TYPE_P(right) != IS_STRING) return compareNonStrings(left, right, out); + + int result = compareScalarValues(right, left, phpVersion, out); + if (result == 1) out = -out; + return result; + } + + if (Z_TYPE_P(right) == IS_STRING) return compareStrings(Z_STR_P(left), Z_STR_P(right), phpVersion, out); + + if (isNullOrBool(right)) { + // null and booleans compare with strings the same way in every PHP version + out = zend_compare(left, right); + return 1; + } + + if (isNan(right)) return 0; + + bool casts; + if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, casts))) return -1; + if (!casts) { + // the string's leading number, or 0 when there is none + zval prefix; + int parsed = pt_string_set_parse_numeric_prefix(Z_STR_P(left), &prefix); + if (UNEXPECTED(parsed < 0)) return -1; + if (parsed == 0) ZVAL_LONG(&prefix, 0); + return compareNonStrings(&prefix, right, out); + } + + zval number; + int overflow; + int parsed = pt_string_set_parse_numeric_string(Z_STR_P(left), phpVersion, &number, overflow); + if (UNEXPECTED(parsed < 0)) return -1; + if (parsed == 1) return compareNonStrings(&number, right, out); + + zend_string *rightString = zval_get_string(right); + out = compareBytes(Z_STR_P(left), rightString); + zend_string_release(rightString); + return 1; + } + + static int compareNonStrings(zval *left, zval *right, zend_long &out) { - return pt_type_call_static(PT_CLASS_LOOSE_COMPARISON_HELPER, lcMethod, len, argc, argv); + if ((isNan(left) || isNan(right)) && !isNullOrBool(left) && !isNullOrBool(right)) return 0; + + // null and booleans compare with NAN as with true + out = zend_compare(left, right); + return 1; } + /* Mirrors compareStrings(): zendi_smart_strcmp() */ + static int compareStrings(zend_string *left, zend_string *right, zval *phpVersion, zend_long &out) + { + zval leftValue, rightValue; + int leftOverflow = 0, rightOverflow = 0; + int leftParsed = pt_string_set_parse_numeric_string(left, phpVersion, &leftValue, leftOverflow); + if (UNEXPECTED(leftParsed < 0)) return -1; + int rightParsed = pt_string_set_parse_numeric_string(right, phpVersion, &rightValue, rightOverflow); + if (UNEXPECTED(rightParsed < 0)) return -1; + if (leftParsed == 0 || rightParsed == 0) { + out = compareBytes(left, right); + return 1; + } + + bool leftFloat = Z_TYPE(leftValue) == IS_DOUBLE; + bool rightFloat = Z_TYPE(rightValue) == IS_DOUBLE; + if (leftOverflow != 0 && leftOverflow == rightOverflow && leftFloat && rightFloat && Z_DVAL(leftValue) == Z_DVAL(rightValue)) { + // integers that overflowed to the same side lose the precision needed to compare them + out = compareBytes(left, right); + return 1; + } + + if (leftFloat || rightFloat) { + if (!leftFloat) { + if (rightOverflow != 0) { + out = -rightOverflow; + return 1; + } + } else if (!rightFloat) { + if (leftOverflow != 0) { + out = leftOverflow; + return 1; + } + } else if (Z_DVAL(leftValue) == Z_DVAL(rightValue) && std::isinf(Z_DVAL(leftValue))) { + out = compareBytes(left, right); + return 1; + } + } + + out = zend_compare(&leftValue, &rightValue); + return 1; + } + + static zend_long compareBytes(zend_string *left, zend_string *right) + { + return ZEND_NORMALIZE_BOOL(zend_binary_strcmp(ZSTR_VAL(left), ZSTR_LEN(left), ZSTR_VAL(right), ZSTR_LEN(right))); + } + + /* }}} */ + /* getNumberString() */ static zv::Val getNumberString(zval *number) { @@ -1785,19 +1894,20 @@ class ValueSetOps if (Z_TYPE_P(value) == IS_TRUE || Z_TYPE_P(value) == IS_FALSE || (Z_TYPE_P(value) == IS_DOUBLE && std::isnan(Z_DVAL_P(value)))) return zv::Val::null(); if (Z_TYPE_P(value) != IS_STRING) return zv::Val::copyOf(zv::Ref(value)); - zv::Args parseArgs{value, phpVersion}; - IETR_VAL(number, looseComparisonHelper(PT_LC("parsenumericstring"), 2, parseArgs)); - if (Z_TYPE_P(number.raw()) == IS_ARRAY) { - zval *parsed = zend_hash_index_find(Z_ARRVAL_P(number.raw()), 0); - return zv::Val::copyOf(zv::Ref(parsed)); - } + zval number; + int overflow; + int parsed = pt_string_set_parse_numeric_string(Z_STR_P(value), phpVersion, &number, overflow); + if (UNEXPECTED(parsed < 0)) return zv::Val(); + if (parsed == 1) return zv::Val::adopt(number); bool casts; if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, casts))) return zv::Val(); if (!casts) { // the number the string starts with - IETR_VAL(prefix, looseComparisonHelper(PT_LC("parsenumericprefix"), 1, value)); - if (Z_TYPE_P(prefix.raw()) == IS_NULL) return zv::Val::integer(0); - return prefix; + zval prefix; + int prefixParsed = pt_string_set_parse_numeric_prefix(Z_STR_P(value), &prefix); + if (UNEXPECTED(prefixParsed < 0)) return zv::Val(); + if (prefixParsed == 0) return zv::Val::integer(0); + return zv::Val::adopt(prefix); } // a number compares with a non-numeric string as its own string @@ -1855,9 +1965,10 @@ class ValueSetOps } if (Z_TYPE_P(value) == IS_STRING) strings.push_back(zv::Val::copyOf(zv::Ref(value))); for (zv::Val &string : strings) { - zv::Args compareArgs{value, string.raw(), phpVersion}; - IETR_VAL(comparison, looseComparisonHelper(PT_LC("comparevalues"), 3, compareArgs)); - if (Z_TYPE_P(comparison.raw()) != IS_LONG || Z_LVAL_P(comparison.raw()) != 0) continue; + zend_long comparison; + int compared = compareScalarValues(value, string.raw(), phpVersion, comparison); + if (UNEXPECTED(compared < 0)) return zv::Val(); + if (compared == 0 || comparison != 0) continue; IETR_VAL(stringType, newConstantStringType(Z_STR_P(string.raw()))); types.push(std::move(stringType)); @@ -2037,20 +2148,23 @@ class ValueSetOps IETR_VAL(looselyEqual, getTypeLooselyEqualTo(value, phpVersion)); types.push(std::move(looselyEqual)); - zv::Args parseArgs{value, phpVersion}; - IETR_VAL(parsed, looseComparisonHelper(PT_LC("parsenumericstring"), 2, parseArgs)); + zval parsedNumber; + int overflow; + int parsed = pt_string_set_parse_numeric_string(string, phpVersion, &parsedNumber, overflow); + if (UNEXPECTED(parsed < 0)) return zv::Val(); zv::Val number; - if (Z_TYPE_P(parsed.raw()) == IS_ARRAY) { - number = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(parsed.raw()), 0))); + if (parsed == 1) { + number = zv::Val::adopt(parsedNumber); IETR_VAL(numericString, createNumericStringType()); types.push(std::move(numericString)); } else { bool casts; if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_CASTS_NUMBERS_TO_STRINGS_ON_LOOSE_COMPARISON, casts))) return zv::Val(); if (!casts) { - number = looseComparisonHelper(PT_LC("parsenumericprefix"), 1, value); - if (UNEXPECTED(number.isUndef())) return zv::Val(); - if (Z_TYPE_P(number.raw()) == IS_NULL) number = zv::Val::integer(0); + zval prefix; + int prefixParsed = pt_string_set_parse_numeric_prefix(string, &prefix); + if (UNEXPECTED(prefixParsed < 0)) return zv::Val(); + number = prefixParsed == 0 ? zv::Val::integer(0) : zv::Val::adopt(prefix); } } if (!number.isNull()) { @@ -2316,6 +2430,11 @@ class ValueSetOps using phpstanturbo::ValueSetOps; using phpstanturbo::ValueSetView; +int pt_value_set_compare_scalar_values(zval *left, zval *right, zval *phpVersion, zend_long &out) +{ + return ValueSetOps::compareScalarValues(left, right, phpVersion, out); +} + zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc, zval *argv) { return pt_type_call_static_ce(pt_ce_value_set, lcFactory, len, argc, argv); @@ -2505,6 +2624,22 @@ PT_MINIT_REGISTRATION(pt_register_value_set) cls.method(sigs::bitwiseShiftLeft, pt_vs_operator>); cls.method(sigs::bitwiseShiftRight, pt_vs_operator>); cls.method(sigs::stringConcatenate, pt_vs_operator); + cls.method(sigs::compareScalarValues, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *left, *right, *phpVersion; + if (!zp::parse(execute_data, left, right, phpVersion)) RETURN_THROWS(); + for (uint32_t i = 0; i < 2; i++) { + zval *value = i == 0 ? left : right; + if (UNEXPECTED(Z_TYPE_P(value) > IS_STRING)) { + zend_argument_type_error(i + 1, "must be of type string|int|float|bool|null, %s given", zend_zval_value_name(value)); + RETURN_THROWS(); + } + } + zend_long comparison; + int compared = ValueSetOps::compareScalarValues(left, right, phpVersion, comparison); + if (UNEXPECTED(compared < 0)) RETURN_THROWS(); + if (compared == 0) RETURN_NULL(); + RETURN_LONG(comparison); + }); cls.shadow(&pt_ce_value_set); } diff --git a/turbo-ext/src/generated/StringSet.h b/turbo-ext/src/generated/StringSet.h index 6932d7461a1..ecf10f1347d 100644 --- a/turbo-ext/src/generated/StringSet.h +++ b/turbo-ext/src/generated/StringSet.h @@ -71,8 +71,12 @@ inline constexpr char strings[] = "afterInteger\0" /* 440 */ "isNumberSuffix\0" /* 453 */ "value\0" /* 468 */ - "isNumeric\0" /* 474 */ - "contains"; /* 484 */ + "parseNumericString\0" /* 474 */ + "parseNumericPrefix\0" /* 493 */ + "matches\0" /* 512 */ + "toNumber\0" /* 520 */ + "isNumeric\0" /* 529 */ + "contains"; /* 539 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* __construct $constants */ reg::packed(10, MAY_BE_NULL, 15), /* __construct $type */ @@ -110,6 +114,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(440, MAY_BE_BOOL), /* isNumberSuffix $afterInteger */ reg::packed(271, 0, 282), /* isNumberSuffix $phpVersion */ reg::packed(67, MAY_BE_BOOL), /* isNumberSuffix return */ + reg::packed(468, MAY_BE_STRING), /* parseNumericString $value */ + reg::packed(271, 0, 282), /* parseNumericString $phpVersion */ + reg::packed(67, MAY_BE_NULL | MAY_BE_ARRAY), /* parseNumericString return */ + reg::packed(468, MAY_BE_STRING), /* parseNumericPrefix $value */ + reg::packed(67, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* parseNumericPrefix return */ + reg::packed(512, MAY_BE_ARRAY), /* toNumber $matches */ + reg::packed(67, MAY_BE_ARRAY), /* toNumber return */ reg::packed(468, MAY_BE_STRING), /* isNumeric $value */ reg::packed(271, 0, 282), /* isNumeric $phpVersion */ reg::packed(67, MAY_BE_BOOL), /* isNumeric return */ @@ -144,8 +155,11 @@ inline constexpr sigtab::Sig isDecimalInteger = { { 387 /* isDecimalInteger */, inline constexpr sigtab::Sig isNumericOrEmpty = { { 404 /* isNumericOrEmpty */, 1, 29, 1, 30, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig canEndInWhitespace = { { 421 /* canEndInWhitespace */, 1, 31, 1, 32, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig isNumberSuffix = { { 453 /* isNumberSuffix */, 2, 33, 2, 35, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isNumeric = { { 474 /* isNumeric */, 2, 36, 2, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig contains = { { 484 /* contains */, 2, 39, 2, 41, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig parseNumericString = { { 474 /* parseNumericString */, 2, 36, 2, 38, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig parseNumericPrefix = { { 493 /* parseNumericPrefix */, 1, 39, 1, 40, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig toNumber = { { 520 /* toNumber */, 1, 41, 1, 42, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isNumeric = { { 529 /* isNumeric */, 2, 43, 2, 45, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig contains = { { 539 /* contains */, 2, 46, 2, 48, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::StringSet diff --git a/turbo-ext/src/generated/ValueSet.h b/turbo-ext/src/generated/ValueSet.h index ebd5d089b3b..1d578c3b7b5 100644 --- a/turbo-ext/src/generated/ValueSet.h +++ b/turbo-ext/src/generated/ValueSet.h @@ -104,96 +104,101 @@ inline constexpr char strings[] = "PHPStan\\Php\\PhpVersion\0" /* 570 */ "getPossiblyLooselyEqualType\0" /* 593 */ "getAlwaysLooselyEqualType\0" /* 621 */ - "right\0" /* 647 */ - "arithmeticAdd\0" /* 653 */ - "arithmeticSubtract\0" /* 667 */ - "arithmeticMultiply\0" /* 686 */ - "arithmeticDivide\0" /* 705 */ - "arithmeticIntDivide\0" /* 722 */ - "getIntegerNumbers\0" /* 742 */ - "arithmeticModulo\0" /* 760 */ - "exponent\0" /* 777 */ - "arithmeticPower\0" /* 786 */ - "powerMember\0" /* 802 */ - "stringConcatenate\0" /* 814 */ - "toStringSet\0" /* 832 */ - "toStringSetOfType\0" /* 844 */ - "isOnlyNonNegativeIntegers\0" /* 862 */ - "getConstantStrings\0" /* 888 */ - "getMembers\0" /* 907 */ - "toNumberSetOfNumber\0" /* 918 */ - "isOnlyFloats\0" /* 938 */ - "isOnlyFloatNumbers\0" /* 951 */ - "value\0" /* 970 */ - "isOnlyInteger\0" /* 976 */ - "createTypeOfNumbers\0" /* 990 */ - "arithmeticNegate\0" /* 1010 */ - "bitwiseAnd\0" /* 1027 */ - "bitwiseOr\0" /* 1038 */ - "bitwiseXor\0" /* 1048 */ - "bitwiseShiftLeft\0" /* 1059 */ - "bitwiseShiftRight\0" /* 1076 */ - "operator\0" /* 1094 */ - "calculateShift\0" /* 1103 */ - "generalizeTooManyConstants\0" /* 1118 */ - "generalize\0" /* 1145 */ - "invalidOrFold\0" /* 1156 */ - "calculateCommonMath\0" /* 1170 */ - "toNumberType\0" /* 1190 */ - "PHPStan\\TrinaryLogic\0" /* 1203 */ - "isArray\0" /* 1224 */ - "isOnlyNonEmptyArrays\0" /* 1232 */ - "calculateBitwise\0" /* 1253 */ - "getResultOfOperands\0" /* 1270 */ - "getZeroDivisorError\0" /* 1290 */ - "result\0" /* 1310 */ - "toResult\0" /* 1317 */ - "fold\0" /* 1326 */ - "left\0" /* 1331 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 1336 */ - "foldValues\0" /* 1368 */ - "base\0" /* 1379 */ - "foldPower\0" /* 1384 */ - "pow\0" /* 1394 */ - "numberType\0" /* 1398 */ - "getConstantNumber\0" /* 1409 */ - "integerType\0" /* 1427 */ - "getConstantInteger\0" /* 1439 */ - "expandIntegerIntervals\0" /* 1458 */ - "getFiniteScalarTypes\0" /* 1481 */ - "withoutArrays\0" /* 1502 */ - "integers\0" /* 1516 */ - "toOperandNumbers\0" /* 1525 */ - "toIntegerNumbers\0" /* 1542 */ - "isOnlyOneIntegerInterval\0" /* 1559 */ - "isOnlyIntegers\0" /* 1584 */ - "isOnlyStrings\0" /* 1599 */ - "mayBeString\0" /* 1613 */ - "mayBeArray\0" /* 1625 */ - "mayBeNonArray\0" /* 1636 */ - "mayBeNonNumber\0" /* 1650 */ - "unionTypes\0" /* 1665 */ - "getNumberValues\0" /* 1676 */ - "getTypeLooselyEqualTo\0" /* 1692 */ - "getLooselyEqualNumber\0" /* 1714 */ - "number\0" /* 1736 */ - "getNumberString\0" /* 1743 */ - "getNumberPartnerType\0" /* 1759 */ - "canBeZero\0" /* 1780 */ - "canBeNonZero\0" /* 1790 */ - "getBooleanAndNullPartnerType\0" /* 1803 */ - "getConstantStringPartnerType\0" /* 1832 */ - "getStringPartnerType\0" /* 1861 */ - "empty\0" /* 1882 */ - "nonEmpty\0" /* 1888 */ - "getArrayPartnerType\0" /* 1897 */ - "createNumberType\0" /* 1917 */ - "min\0" /* 1934 */ - "max\0" /* 1938 */ - "getIntegerTypeInInterval\0" /* 1942 */ - "createNumberLikeObjectType\0" /* 1967 */ - "createStringableObjectType\0" /* 1994 */ - "createNumericStringType"; /* 2021 */ + "left\0" /* 647 */ + "right\0" /* 652 */ + "compareScalarValues\0" /* 658 */ + "compareNonStrings\0" /* 678 */ + "compareStrings\0" /* 696 */ + "value\0" /* 711 */ + "isNan\0" /* 717 */ + "compareBytes\0" /* 723 */ + "arithmeticAdd\0" /* 736 */ + "arithmeticSubtract\0" /* 750 */ + "arithmeticMultiply\0" /* 769 */ + "arithmeticDivide\0" /* 788 */ + "arithmeticIntDivide\0" /* 805 */ + "getIntegerNumbers\0" /* 825 */ + "arithmeticModulo\0" /* 843 */ + "exponent\0" /* 860 */ + "arithmeticPower\0" /* 869 */ + "powerMember\0" /* 885 */ + "stringConcatenate\0" /* 897 */ + "toStringSet\0" /* 915 */ + "toStringSetOfType\0" /* 927 */ + "isOnlyNonNegativeIntegers\0" /* 945 */ + "getConstantStrings\0" /* 971 */ + "getMembers\0" /* 990 */ + "toNumberSetOfNumber\0" /* 1001 */ + "isOnlyFloats\0" /* 1021 */ + "isOnlyFloatNumbers\0" /* 1034 */ + "isOnlyInteger\0" /* 1053 */ + "createTypeOfNumbers\0" /* 1067 */ + "arithmeticNegate\0" /* 1087 */ + "bitwiseAnd\0" /* 1104 */ + "bitwiseOr\0" /* 1115 */ + "bitwiseXor\0" /* 1125 */ + "bitwiseShiftLeft\0" /* 1136 */ + "bitwiseShiftRight\0" /* 1153 */ + "operator\0" /* 1171 */ + "calculateShift\0" /* 1180 */ + "generalizeTooManyConstants\0" /* 1195 */ + "generalize\0" /* 1222 */ + "invalidOrFold\0" /* 1233 */ + "calculateCommonMath\0" /* 1247 */ + "toNumberType\0" /* 1267 */ + "PHPStan\\TrinaryLogic\0" /* 1280 */ + "isArray\0" /* 1301 */ + "isOnlyNonEmptyArrays\0" /* 1309 */ + "calculateBitwise\0" /* 1330 */ + "getResultOfOperands\0" /* 1347 */ + "getZeroDivisorError\0" /* 1367 */ + "result\0" /* 1387 */ + "toResult\0" /* 1394 */ + "fold\0" /* 1403 */ + "PHPStan\\Type\\ConstantScalarType\0" /* 1408 */ + "foldValues\0" /* 1440 */ + "base\0" /* 1451 */ + "foldPower\0" /* 1456 */ + "pow\0" /* 1466 */ + "numberType\0" /* 1470 */ + "getConstantNumber\0" /* 1481 */ + "integerType\0" /* 1499 */ + "getConstantInteger\0" /* 1511 */ + "expandIntegerIntervals\0" /* 1530 */ + "getFiniteScalarTypes\0" /* 1553 */ + "withoutArrays\0" /* 1574 */ + "integers\0" /* 1588 */ + "toOperandNumbers\0" /* 1597 */ + "toIntegerNumbers\0" /* 1614 */ + "isOnlyOneIntegerInterval\0" /* 1631 */ + "isOnlyIntegers\0" /* 1656 */ + "isOnlyStrings\0" /* 1671 */ + "mayBeString\0" /* 1685 */ + "mayBeArray\0" /* 1697 */ + "mayBeNonArray\0" /* 1708 */ + "mayBeNonNumber\0" /* 1722 */ + "unionTypes\0" /* 1737 */ + "getNumberValues\0" /* 1748 */ + "getTypeLooselyEqualTo\0" /* 1764 */ + "getLooselyEqualNumber\0" /* 1786 */ + "number\0" /* 1808 */ + "getNumberString\0" /* 1815 */ + "getNumberPartnerType\0" /* 1831 */ + "canBeZero\0" /* 1852 */ + "canBeNonZero\0" /* 1862 */ + "getBooleanAndNullPartnerType\0" /* 1875 */ + "getConstantStringPartnerType\0" /* 1904 */ + "getStringPartnerType\0" /* 1933 */ + "empty\0" /* 1954 */ + "nonEmpty\0" /* 1960 */ + "getArrayPartnerType\0" /* 1969 */ + "createNumberType\0" /* 1989 */ + "min\0" /* 2006 */ + "max\0" /* 2010 */ + "getIntegerTypeInInterval\0" /* 2014 */ + "createNumberLikeObjectType\0" /* 2039 */ + "createStringableObjectType\0" /* 2066 */ + "createNumericStringType"; /* 2093 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL), /* __construct $unknown */ reg::packed(8, MAY_BE_BOOL, reg::NoString, false, false, 13), /* __construct $null */ @@ -243,32 +248,48 @@ inline constexpr reg::PackedArg args[] = { reg::packed(254, 0, 118), /* getPossiblyLooselyEqualType return */ reg::packed(559, 0, 570), /* getAlwaysLooselyEqualType $phpVersion */ reg::packed(254, 0, 118), /* getAlwaysLooselyEqualType return */ - reg::packed(647, 0, 255), /* arithmeticAdd $right */ + reg::packed(647, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* compareScalarValues $left */ + reg::packed(652, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* compareScalarValues $right */ + reg::packed(559, 0, 570), /* compareScalarValues $phpVersion */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG), /* compareScalarValues return */ + reg::packed(647, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE), /* compareNonStrings $left */ + reg::packed(652, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE), /* compareNonStrings $right */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG), /* compareNonStrings return */ + reg::packed(647, MAY_BE_STRING), /* compareStrings $left */ + reg::packed(652, MAY_BE_STRING), /* compareStrings $right */ + reg::packed(559, 0, 570), /* compareStrings $phpVersion */ + reg::packed(254, MAY_BE_LONG), /* compareStrings return */ + reg::packed(711, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE), /* isNan $value */ + reg::packed(254, MAY_BE_BOOL), /* isNan return */ + reg::packed(647, MAY_BE_STRING), /* compareBytes $left */ + reg::packed(652, MAY_BE_STRING), /* compareBytes $right */ + reg::packed(254, MAY_BE_LONG), /* compareBytes return */ + reg::packed(652, 0, 255), /* arithmeticAdd $right */ reg::packed(559, 0, 570), /* arithmeticAdd $phpVersion */ reg::packed(254, 0, 118), /* arithmeticAdd return */ - reg::packed(647, 0, 255), /* arithmeticSubtract $right */ + reg::packed(652, 0, 255), /* arithmeticSubtract $right */ reg::packed(559, 0, 570), /* arithmeticSubtract $phpVersion */ reg::packed(254, 0, 118), /* arithmeticSubtract return */ - reg::packed(647, 0, 255), /* arithmeticMultiply $right */ + reg::packed(652, 0, 255), /* arithmeticMultiply $right */ reg::packed(559, 0, 570), /* arithmeticMultiply $phpVersion */ reg::packed(254, 0, 118), /* arithmeticMultiply return */ - reg::packed(647, 0, 255), /* arithmeticDivide $right */ + reg::packed(652, 0, 255), /* arithmeticDivide $right */ reg::packed(559, 0, 570), /* arithmeticDivide $phpVersion */ reg::packed(254, 0, 118), /* arithmeticDivide return */ - reg::packed(647, 0, 255), /* arithmeticIntDivide $right */ + reg::packed(652, 0, 255), /* arithmeticIntDivide $right */ reg::packed(559, 0, 570), /* arithmeticIntDivide $phpVersion */ reg::packed(254, 0, 118), /* arithmeticIntDivide return */ reg::packed(254, MAY_BE_NULL, 32), /* getIntegerNumbers return */ - reg::packed(647, 0, 255), /* arithmeticModulo $right */ + reg::packed(652, 0, 255), /* arithmeticModulo $right */ reg::packed(559, 0, 570), /* arithmeticModulo $phpVersion */ reg::packed(254, 0, 118), /* arithmeticModulo return */ - reg::packed(777, 0, 255), /* arithmeticPower $exponent */ + reg::packed(860, 0, 255), /* arithmeticPower $exponent */ reg::packed(559, 0, 570), /* arithmeticPower $phpVersion */ reg::packed(254, 0, 118), /* arithmeticPower return */ - reg::packed(777, 0, 255), /* powerMember $exponent */ + reg::packed(860, 0, 255), /* powerMember $exponent */ reg::packed(559, 0, 570), /* powerMember $phpVersion */ reg::packed(254, 0, 118), /* powerMember return */ - reg::packed(647, 0, 255), /* stringConcatenate $right */ + reg::packed(652, 0, 255), /* stringConcatenate $right */ reg::packed(559, 0, 570), /* stringConcatenate $phpVersion */ reg::packed(254, 0, 118), /* stringConcatenate return */ reg::packed(254, MAY_BE_NULL, 63), /* toStringSet return */ @@ -284,75 +305,75 @@ inline constexpr reg::PackedArg args[] = { reg::packed(24, 0, 32), /* isOnlyFloatNumbers $numbers */ reg::packed(254, MAY_BE_BOOL), /* isOnlyFloatNumbers return */ reg::packed(24, 0, 32), /* isOnlyInteger $numbers */ - reg::packed(970, MAY_BE_LONG), /* isOnlyInteger $value */ + reg::packed(711, MAY_BE_LONG), /* isOnlyInteger $value */ reg::packed(254, MAY_BE_BOOL), /* isOnlyInteger return */ reg::packed(24, 0, 32), /* createTypeOfNumbers $numbers */ reg::packed(254, 0, 118), /* createTypeOfNumbers return */ reg::packed(559, 0, 570), /* arithmeticNegate $phpVersion */ reg::packed(254, 0, 118), /* arithmeticNegate return */ - reg::packed(647, 0, 255), /* bitwiseAnd $right */ + reg::packed(652, 0, 255), /* bitwiseAnd $right */ reg::packed(559, 0, 570), /* bitwiseAnd $phpVersion */ reg::packed(254, 0, 118), /* bitwiseAnd return */ - reg::packed(647, 0, 255), /* bitwiseOr $right */ + reg::packed(652, 0, 255), /* bitwiseOr $right */ reg::packed(559, 0, 570), /* bitwiseOr $phpVersion */ reg::packed(254, 0, 118), /* bitwiseOr return */ - reg::packed(647, 0, 255), /* bitwiseXor $right */ + reg::packed(652, 0, 255), /* bitwiseXor $right */ reg::packed(559, 0, 570), /* bitwiseXor $phpVersion */ reg::packed(254, 0, 118), /* bitwiseXor return */ - reg::packed(647, 0, 255), /* bitwiseShiftLeft $right */ + reg::packed(652, 0, 255), /* bitwiseShiftLeft $right */ reg::packed(559, 0, 570), /* bitwiseShiftLeft $phpVersion */ reg::packed(254, 0, 118), /* bitwiseShiftLeft return */ - reg::packed(647, 0, 255), /* bitwiseShiftRight $right */ + reg::packed(652, 0, 255), /* bitwiseShiftRight $right */ reg::packed(559, 0, 570), /* bitwiseShiftRight $phpVersion */ reg::packed(254, 0, 118), /* bitwiseShiftRight return */ - reg::packed(1094, MAY_BE_STRING), /* calculateShift $operator */ - reg::packed(647, 0, 255), /* calculateShift $right */ + reg::packed(1171, MAY_BE_STRING), /* calculateShift $operator */ + reg::packed(652, 0, 255), /* calculateShift $right */ reg::packed(559, 0, 570), /* calculateShift $phpVersion */ reg::packed(254, 0, 118), /* calculateShift return */ - reg::packed(647, 0, 255), /* generalizeTooManyConstants $right */ + reg::packed(652, 0, 255), /* generalizeTooManyConstants $right */ reg::packed(254, MAY_BE_ARRAY), /* generalizeTooManyConstants return */ reg::packed(254, 0, 255), /* generalize return */ - reg::packed(1094, MAY_BE_STRING), /* invalidOrFold $operator */ - reg::packed(647, 0, 255), /* invalidOrFold $right */ + reg::packed(1171, MAY_BE_STRING), /* invalidOrFold $operator */ + reg::packed(652, 0, 255), /* invalidOrFold $right */ reg::packed(254, MAY_BE_NULL, 118), /* invalidOrFold return */ - reg::packed(1094, MAY_BE_STRING), /* calculateCommonMath $operator */ - reg::packed(647, 0, 255), /* calculateCommonMath $right */ + reg::packed(1171, MAY_BE_STRING), /* calculateCommonMath $operator */ + reg::packed(652, 0, 255), /* calculateCommonMath $right */ reg::packed(559, 0, 570), /* calculateCommonMath $phpVersion */ reg::packed(254, 0, 118), /* calculateCommonMath return */ reg::packed(254, MAY_BE_NULL, 118), /* toNumberType return */ - reg::packed(254, 0, 1203), /* isArray return */ + reg::packed(254, 0, 1280), /* isArray return */ reg::packed(254, MAY_BE_BOOL), /* isOnlyNonEmptyArrays return */ - reg::packed(1094, MAY_BE_STRING), /* calculateBitwise $operator */ - reg::packed(647, 0, 255), /* calculateBitwise $right */ + reg::packed(1171, MAY_BE_STRING), /* calculateBitwise $operator */ + reg::packed(652, 0, 255), /* calculateBitwise $right */ reg::packed(559, 0, 570), /* calculateBitwise $phpVersion */ reg::packed(254, 0, 118), /* calculateBitwise return */ - reg::packed(647, 0, 255), /* getResultOfOperands $right */ + reg::packed(652, 0, 255), /* getResultOfOperands $right */ reg::packed(254, MAY_BE_NULL, 118), /* getResultOfOperands return */ reg::packed(254, MAY_BE_NULL, 118), /* getZeroDivisorError return */ - reg::packed(647, 0, 255), /* toResult $right */ - reg::packed(1310, 0, 118), /* toResult $result */ + reg::packed(652, 0, 255), /* toResult $right */ + reg::packed(1387, 0, 118), /* toResult $result */ reg::packed(254, 0, 118), /* toResult return */ - reg::packed(1094, MAY_BE_STRING), /* fold $operator */ - reg::packed(647, 0, 255), /* fold $right */ + reg::packed(1171, MAY_BE_STRING), /* fold $operator */ + reg::packed(652, 0, 255), /* fold $right */ reg::packed(254, MAY_BE_NULL, 118), /* fold return */ - reg::packed(1094, MAY_BE_STRING), /* foldValues $operator */ - reg::packed(1331, 0, 1336), /* foldValues $left */ - reg::packed(647, 0, 1336), /* foldValues $right */ + reg::packed(1171, MAY_BE_STRING), /* foldValues $operator */ + reg::packed(647, 0, 1408), /* foldValues $left */ + reg::packed(652, 0, 1408), /* foldValues $right */ reg::packed(254, MAY_BE_NULL, 118), /* foldValues return */ - reg::packed(1379, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $base */ - reg::packed(777, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $exponent */ + reg::packed(1451, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $base */ + reg::packed(860, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* foldPower $exponent */ reg::packed(254, MAY_BE_NULL, 118), /* foldPower return */ - reg::packed(1379, MAY_BE_ANY), /* pow $base */ - reg::packed(777, MAY_BE_ANY), /* pow $exponent */ + reg::packed(1451, MAY_BE_ANY), /* pow $base */ + reg::packed(860, MAY_BE_ANY), /* pow $exponent */ reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* pow return */ - reg::packed(1398, 0, 118), /* getConstantNumber $numberType */ + reg::packed(1470, 0, 118), /* getConstantNumber $numberType */ reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getConstantNumber return */ - reg::packed(1427, 0, 118), /* getConstantInteger $integerType */ + reg::packed(1499, 0, 118), /* getConstantInteger $integerType */ reg::packed(254, MAY_BE_NULL | MAY_BE_LONG), /* getConstantInteger return */ - reg::packed(1458, MAY_BE_BOOL), /* getFiniteScalarTypes $expandIntegerIntervals */ + reg::packed(1530, MAY_BE_BOOL), /* getFiniteScalarTypes $expandIntegerIntervals */ reg::packed(254, MAY_BE_NULL | MAY_BE_ARRAY), /* getFiniteScalarTypes return */ - reg::packed(1502, MAY_BE_BOOL), /* toOperandNumbers $withoutArrays */ - reg::packed(1516, MAY_BE_BOOL), /* toOperandNumbers $integers */ + reg::packed(1574, MAY_BE_BOOL), /* toOperandNumbers $withoutArrays */ + reg::packed(1588, MAY_BE_BOOL), /* toOperandNumbers $integers */ reg::packed(254, MAY_BE_NULL, 32), /* toOperandNumbers return */ reg::packed(24, 0, 32), /* toIntegerNumbers $numbers */ reg::packed(254, 0, 32), /* toIntegerNumbers return */ @@ -363,18 +384,18 @@ inline constexpr reg::PackedArg args[] = { reg::packed(254, MAY_BE_BOOL), /* mayBeArray return */ reg::packed(254, MAY_BE_BOOL), /* mayBeNonArray return */ reg::packed(254, MAY_BE_BOOL), /* mayBeNonNumber return */ - reg::packed(1331, MAY_BE_NULL, 118), /* unionTypes $left */ - reg::packed(647, MAY_BE_NULL, 118), /* unionTypes $right */ + reg::packed(647, MAY_BE_NULL, 118), /* unionTypes $left */ + reg::packed(652, MAY_BE_NULL, 118), /* unionTypes $right */ reg::packed(254, MAY_BE_NULL, 118), /* unionTypes return */ reg::packed(24, 0, 32), /* getNumberValues $numbers */ reg::packed(254, MAY_BE_NULL | MAY_BE_ARRAY), /* getNumberValues return */ - reg::packed(970, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getTypeLooselyEqualTo $value */ + reg::packed(711, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getTypeLooselyEqualTo $value */ reg::packed(559, 0, 570), /* getTypeLooselyEqualTo $phpVersion */ reg::packed(254, 0, 118), /* getTypeLooselyEqualTo return */ - reg::packed(970, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getLooselyEqualNumber $value */ + reg::packed(711, MAY_BE_NULL | MAY_BE_BOOL | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING), /* getLooselyEqualNumber $value */ reg::packed(559, 0, 570), /* getLooselyEqualNumber $phpVersion */ reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getLooselyEqualNumber return */ - reg::packed(1736, MAY_BE_LONG | MAY_BE_DOUBLE), /* getNumberString $number */ + reg::packed(1808, MAY_BE_LONG | MAY_BE_DOUBLE), /* getNumberString $number */ reg::packed(254, MAY_BE_STRING), /* getNumberString return */ reg::packed(24, 0, 32), /* getNumberPartnerType $numbers */ reg::packed(559, 0, 570), /* getNumberPartnerType $phpVersion */ @@ -383,22 +404,22 @@ inline constexpr reg::PackedArg args[] = { reg::packed(254, MAY_BE_BOOL), /* canBeZero return */ reg::packed(24, 0, 32), /* canBeNonZero $numbers */ reg::packed(254, MAY_BE_BOOL), /* canBeNonZero return */ - reg::packed(1780, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeZero */ - reg::packed(1790, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeNonZero */ + reg::packed(1852, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeZero */ + reg::packed(1862, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeNonZero */ reg::packed(254, 0, 118), /* getBooleanAndNullPartnerType return */ - reg::packed(970, MAY_BE_STRING), /* getConstantStringPartnerType $value */ + reg::packed(711, MAY_BE_STRING), /* getConstantStringPartnerType $value */ reg::packed(559, 0, 570), /* getConstantStringPartnerType $phpVersion */ reg::packed(254, 0, 118), /* getConstantStringPartnerType return */ reg::packed(55, 0, 118), /* getStringPartnerType $strings */ reg::packed(559, 0, 570), /* getStringPartnerType $phpVersion */ reg::packed(254, 0, 118), /* getStringPartnerType return */ - reg::packed(1882, MAY_BE_BOOL), /* getArrayPartnerType $empty */ - reg::packed(1888, MAY_BE_BOOL), /* getArrayPartnerType $nonEmpty */ + reg::packed(1954, MAY_BE_BOOL), /* getArrayPartnerType $empty */ + reg::packed(1960, MAY_BE_BOOL), /* getArrayPartnerType $nonEmpty */ reg::packed(254, 0, 118), /* getArrayPartnerType return */ - reg::packed(1736, MAY_BE_LONG | MAY_BE_DOUBLE), /* createNumberType $number */ + reg::packed(1808, MAY_BE_LONG | MAY_BE_DOUBLE), /* createNumberType $number */ reg::packed(254, 0, 118), /* createNumberType return */ - reg::packed(1934, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $min */ - reg::packed(1938, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $max */ + reg::packed(2006, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $min */ + reg::packed(2010, MAY_BE_DOUBLE), /* getIntegerTypeInInterval $max */ reg::packed(254, 0, 118), /* getIntegerTypeInInterval return */ reg::packed(559, 0, 570), /* createNumberLikeObjectType $phpVersion */ reg::packed(254, 0, 118), /* createNumberLikeObjectType return */ @@ -432,77 +453,82 @@ inline constexpr sigtab::Sig union_ = { { 543 /* union */, 1, 40, 1, 41, ZEND_AC inline constexpr sigtab::Sig intersect = { { 549 /* intersect */, 1, 42, 1, 43, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getPossiblyLooselyEqualType = { { 593 /* getPossiblyLooselyEqualType */, 1, 44, 1, 45, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getAlwaysLooselyEqualType = { { 621 /* getAlwaysLooselyEqualType */, 1, 46, 1, 47, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticAdd = { { 653 /* arithmeticAdd */, 2, 48, 2, 50, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticSubtract = { { 667 /* arithmeticSubtract */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticMultiply = { { 686 /* arithmeticMultiply */, 2, 54, 2, 56, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticDivide = { { 705 /* arithmeticDivide */, 2, 57, 2, 59, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticIntDivide = { { 722 /* arithmeticIntDivide */, 2, 60, 2, 62, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getIntegerNumbers = { { 742 /* getIntegerNumbers */, 0, 63, 0, 63, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig arithmeticModulo = { { 760 /* arithmeticModulo */, 2, 64, 2, 66, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig arithmeticPower = { { 786 /* arithmeticPower */, 2, 67, 2, 69, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig powerMember = { { 802 /* powerMember */, 2, 70, 2, 72, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig stringConcatenate = { { 814 /* stringConcatenate */, 2, 73, 2, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig toStringSet = { { 832 /* toStringSet */, 0, 76, 0, 76, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toStringSetOfType = { { 844 /* toStringSetOfType */, 1, 77, 1, 78, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isOnlyNonNegativeIntegers = { { 862 /* isOnlyNonNegativeIntegers */, 1, 79, 1, 80, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantStrings = { { 888 /* getConstantStrings */, 0, 81, 0, 81, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getMembers = { { 907 /* getMembers */, 0, 82, 0, 82, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toNumberSetOfNumber = { { 918 /* toNumberSetOfNumber */, 0, 83, 0, 83, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyFloats = { { 938 /* isOnlyFloats */, 1, 84, 1, 85, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyFloatNumbers = { { 951 /* isOnlyFloatNumbers */, 1, 86, 1, 87, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isOnlyInteger = { { 976 /* isOnlyInteger */, 2, 88, 2, 90, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createTypeOfNumbers = { { 990 /* createTypeOfNumbers */, 1, 91, 1, 92, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arithmeticNegate = { { 1010 /* arithmeticNegate */, 1, 93, 1, 94, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseAnd = { { 1027 /* bitwiseAnd */, 2, 95, 2, 97, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseOr = { { 1038 /* bitwiseOr */, 2, 98, 2, 100, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseXor = { { 1048 /* bitwiseXor */, 2, 101, 2, 103, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftLeft = { { 1059 /* bitwiseShiftLeft */, 2, 104, 2, 106, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig bitwiseShiftRight = { { 1076 /* bitwiseShiftRight */, 2, 107, 2, 109, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig calculateShift = { { 1103 /* calculateShift */, 3, 110, 3, 113, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig generalizeTooManyConstants = { { 1118 /* generalizeTooManyConstants */, 1, 114, 1, 115, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig generalize = { { 1145 /* generalize */, 0, 116, 0, 116, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig invalidOrFold = { { 1156 /* invalidOrFold */, 2, 117, 2, 119, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig calculateCommonMath = { { 1170 /* calculateCommonMath */, 3, 120, 3, 123, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toNumberType = { { 1190 /* toNumberType */, 0, 124, 0, 124, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isArray = { { 1224 /* isArray */, 0, 125, 0, 125, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyNonEmptyArrays = { { 1232 /* isOnlyNonEmptyArrays */, 0, 126, 0, 126, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig calculateBitwise = { { 1253 /* calculateBitwise */, 3, 127, 3, 130, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getResultOfOperands = { { 1270 /* getResultOfOperands */, 1, 131, 1, 132, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getZeroDivisorError = { { 1290 /* getZeroDivisorError */, 0, 133, 0, 133, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toResult = { { 1317 /* toResult */, 2, 134, 2, 136, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig fold = { { 1326 /* fold */, 2, 137, 2, 139, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig foldValues = { { 1368 /* foldValues */, 3, 140, 3, 143, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig foldPower = { { 1384 /* foldPower */, 2, 144, 2, 146, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig pow = { { 1394 /* pow */, 2, 147, 2, 149, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantNumber = { { 1409 /* getConstantNumber */, 1, 150, 1, 151, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantInteger = { { 1439 /* getConstantInteger */, 1, 152, 1, 153, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFiniteScalarTypes = { { 1481 /* getFiniteScalarTypes */, 1, 154, 1, 155, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toOperandNumbers = { { 1525 /* toOperandNumbers */, 2, 156, 2, 158, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig toIntegerNumbers = { { 1542 /* toIntegerNumbers */, 1, 159, 1, 160, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isOnlyOneIntegerInterval = { { 1559 /* isOnlyOneIntegerInterval */, 0, 161, 0, 161, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyIntegers = { { 1584 /* isOnlyIntegers */, 0, 162, 0, 162, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOnlyStrings = { { 1599 /* isOnlyStrings */, 0, 163, 0, 163, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeString = { { 1613 /* mayBeString */, 0, 164, 0, 164, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeArray = { { 1625 /* mayBeArray */, 0, 165, 0, 165, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeNonArray = { { 1636 /* mayBeNonArray */, 0, 166, 0, 166, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mayBeNonNumber = { { 1650 /* mayBeNonNumber */, 0, 167, 0, 167, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig unionTypes = { { 1665 /* unionTypes */, 2, 168, 2, 170, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNumberValues = { { 1676 /* getNumberValues */, 1, 171, 1, 172, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getTypeLooselyEqualTo = { { 1692 /* getTypeLooselyEqualTo */, 2, 173, 2, 175, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getLooselyEqualNumber = { { 1714 /* getLooselyEqualNumber */, 2, 176, 2, 178, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNumberString = { { 1743 /* getNumberString */, 1, 179, 1, 180, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNumberPartnerType = { { 1759 /* getNumberPartnerType */, 2, 181, 2, 183, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig canBeZero = { { 1780 /* canBeZero */, 1, 184, 1, 185, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig canBeNonZero = { { 1790 /* canBeNonZero */, 1, 186, 1, 187, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getBooleanAndNullPartnerType = { { 1803 /* getBooleanAndNullPartnerType */, 2, 188, 2, 190, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getConstantStringPartnerType = { { 1832 /* getConstantStringPartnerType */, 2, 191, 2, 193, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getStringPartnerType = { { 1861 /* getStringPartnerType */, 2, 194, 2, 196, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getArrayPartnerType = { { 1897 /* getArrayPartnerType */, 2, 197, 2, 199, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumberType = { { 1917 /* createNumberType */, 1, 200, 1, 201, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerTypeInInterval = { { 1942 /* getIntegerTypeInInterval */, 2, 202, 2, 204, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumberLikeObjectType = { { 1967 /* createNumberLikeObjectType */, 1, 205, 1, 206, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createStringableObjectType = { { 1994 /* createStringableObjectType */, 1, 207, 1, 208, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createNumericStringType = { { 2021 /* createNumericStringType */, 0, 209, 0, 209, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig compareScalarValues = { { 658 /* compareScalarValues */, 3, 48, 3, 51, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig compareNonStrings = { { 678 /* compareNonStrings */, 2, 52, 2, 54, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig compareStrings = { { 696 /* compareStrings */, 3, 55, 3, 58, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isNan = { { 717 /* isNan */, 1, 59, 1, 60, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig compareBytes = { { 723 /* compareBytes */, 2, 61, 2, 63, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arithmeticAdd = { { 736 /* arithmeticAdd */, 2, 64, 2, 66, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticSubtract = { { 750 /* arithmeticSubtract */, 2, 67, 2, 69, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticMultiply = { { 769 /* arithmeticMultiply */, 2, 70, 2, 72, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticDivide = { { 788 /* arithmeticDivide */, 2, 73, 2, 75, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticIntDivide = { { 805 /* arithmeticIntDivide */, 2, 76, 2, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getIntegerNumbers = { { 825 /* getIntegerNumbers */, 0, 79, 0, 79, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig arithmeticModulo = { { 843 /* arithmeticModulo */, 2, 80, 2, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig arithmeticPower = { { 869 /* arithmeticPower */, 2, 83, 2, 85, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig powerMember = { { 885 /* powerMember */, 2, 86, 2, 88, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig stringConcatenate = { { 897 /* stringConcatenate */, 2, 89, 2, 91, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig toStringSet = { { 915 /* toStringSet */, 0, 92, 0, 92, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toStringSetOfType = { { 927 /* toStringSetOfType */, 1, 93, 1, 94, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyNonNegativeIntegers = { { 945 /* isOnlyNonNegativeIntegers */, 1, 95, 1, 96, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantStrings = { { 971 /* getConstantStrings */, 0, 97, 0, 97, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getMembers = { { 990 /* getMembers */, 0, 98, 0, 98, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toNumberSetOfNumber = { { 1001 /* toNumberSetOfNumber */, 0, 99, 0, 99, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyFloats = { { 1021 /* isOnlyFloats */, 1, 100, 1, 101, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyFloatNumbers = { { 1034 /* isOnlyFloatNumbers */, 1, 102, 1, 103, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyInteger = { { 1053 /* isOnlyInteger */, 2, 104, 2, 106, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createTypeOfNumbers = { { 1067 /* createTypeOfNumbers */, 1, 107, 1, 108, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig arithmeticNegate = { { 1087 /* arithmeticNegate */, 1, 109, 1, 110, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseAnd = { { 1104 /* bitwiseAnd */, 2, 111, 2, 113, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseOr = { { 1115 /* bitwiseOr */, 2, 114, 2, 116, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseXor = { { 1125 /* bitwiseXor */, 2, 117, 2, 119, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftLeft = { { 1136 /* bitwiseShiftLeft */, 2, 120, 2, 122, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig bitwiseShiftRight = { { 1153 /* bitwiseShiftRight */, 2, 123, 2, 125, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig calculateShift = { { 1180 /* calculateShift */, 3, 126, 3, 129, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeTooManyConstants = { { 1195 /* generalizeTooManyConstants */, 1, 130, 1, 131, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalize = { { 1222 /* generalize */, 0, 132, 0, 132, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invalidOrFold = { { 1233 /* invalidOrFold */, 2, 133, 2, 135, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig calculateCommonMath = { { 1247 /* calculateCommonMath */, 3, 136, 3, 139, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toNumberType = { { 1267 /* toNumberType */, 0, 140, 0, 140, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isArray = { { 1301 /* isArray */, 0, 141, 0, 141, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyNonEmptyArrays = { { 1309 /* isOnlyNonEmptyArrays */, 0, 142, 0, 142, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig calculateBitwise = { { 1330 /* calculateBitwise */, 3, 143, 3, 146, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getResultOfOperands = { { 1347 /* getResultOfOperands */, 1, 147, 1, 148, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getZeroDivisorError = { { 1367 /* getZeroDivisorError */, 0, 149, 0, 149, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toResult = { { 1394 /* toResult */, 2, 150, 2, 152, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig fold = { { 1403 /* fold */, 2, 153, 2, 155, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig foldValues = { { 1440 /* foldValues */, 3, 156, 3, 159, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig foldPower = { { 1456 /* foldPower */, 2, 160, 2, 162, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig pow = { { 1466 /* pow */, 2, 163, 2, 165, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantNumber = { { 1481 /* getConstantNumber */, 1, 166, 1, 167, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantInteger = { { 1511 /* getConstantInteger */, 1, 168, 1, 169, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFiniteScalarTypes = { { 1553 /* getFiniteScalarTypes */, 1, 170, 1, 171, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toOperandNumbers = { { 1597 /* toOperandNumbers */, 2, 172, 2, 174, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig toIntegerNumbers = { { 1614 /* toIntegerNumbers */, 1, 175, 1, 176, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isOnlyOneIntegerInterval = { { 1631 /* isOnlyOneIntegerInterval */, 0, 177, 0, 177, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyIntegers = { { 1656 /* isOnlyIntegers */, 0, 178, 0, 178, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOnlyStrings = { { 1671 /* isOnlyStrings */, 0, 179, 0, 179, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeString = { { 1685 /* mayBeString */, 0, 180, 0, 180, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeArray = { { 1697 /* mayBeArray */, 0, 181, 0, 181, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeNonArray = { { 1708 /* mayBeNonArray */, 0, 182, 0, 182, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mayBeNonNumber = { { 1722 /* mayBeNonNumber */, 0, 183, 0, 183, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig unionTypes = { { 1737 /* unionTypes */, 2, 184, 2, 186, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberValues = { { 1748 /* getNumberValues */, 1, 187, 1, 188, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getTypeLooselyEqualTo = { { 1764 /* getTypeLooselyEqualTo */, 2, 189, 2, 191, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getLooselyEqualNumber = { { 1786 /* getLooselyEqualNumber */, 2, 192, 2, 194, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberString = { { 1815 /* getNumberString */, 1, 195, 1, 196, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNumberPartnerType = { { 1831 /* getNumberPartnerType */, 2, 197, 2, 199, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canBeZero = { { 1852 /* canBeZero */, 1, 200, 1, 201, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig canBeNonZero = { { 1862 /* canBeNonZero */, 1, 202, 1, 203, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getBooleanAndNullPartnerType = { { 1875 /* getBooleanAndNullPartnerType */, 2, 204, 2, 206, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantStringPartnerType = { { 1904 /* getConstantStringPartnerType */, 2, 207, 2, 209, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getStringPartnerType = { { 1933 /* getStringPartnerType */, 2, 210, 2, 212, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getArrayPartnerType = { { 1969 /* getArrayPartnerType */, 2, 213, 2, 215, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumberType = { { 1989 /* createNumberType */, 1, 216, 1, 217, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getIntegerTypeInInterval = { { 2014 /* getIntegerTypeInInterval */, 2, 218, 2, 220, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumberLikeObjectType = { { 2039 /* createNumberLikeObjectType */, 1, 221, 1, 222, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createStringableObjectType = { { 2066 /* createStringableObjectType */, 1, 223, 1, 224, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createNumericStringType = { { 2093 /* createNumericStringType */, 0, 225, 0, 225, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::ValueSet diff --git a/turbo-ext/src/support.cpp b/turbo-ext/src/support.cpp index 5cb546e775c..e1dd7bc052a 100644 --- a/turbo-ext/src/support.cpp +++ b/turbo-ext/src/support.cpp @@ -51,7 +51,6 @@ static const pt_class_template pt_class_templates[PT_CLASS_COUNT] = { /* PT_CLASS_TYPE */ {"type", "PHPStan\\Type\\Type"}, /* PT_CLASS_CLASS_NAME_TO_OBJECT_TYPE_RESULT */ {"classNameToObjectTypeResult", "PHPStan\\Type\\ClassNameToObjectTypeResult"}, /* PT_CLASS_IDENTIFIER_TYPE_NODE */ {"identifierTypeNode", "PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode"}, - /* PT_CLASS_LOOSE_COMPARISON_HELPER */ {"looseComparisonHelper", "PHPStan\\Type\\LooseComparisonHelper"}, /* PT_CLASS_COMPOUND_TYPE */ {"compoundType", "PHPStan\\Type\\CompoundType"}, /* PT_CLASS_CONSTANT_SCALAR_TYPE */ {"constantScalarType", "PHPStan\\Type\\ConstantScalarType"}, /* PT_CLASS_GENERIC_TYPE_NODE */ {"genericTypeNode", "PHPStan\\PhpDocParser\\Ast\\Type\\GenericTypeNode"}, diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index a6014ef1d82..d105b6105e7 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -137,7 +137,6 @@ enum { PT_CLASS_TYPE, PT_CLASS_CLASS_NAME_TO_OBJECT_TYPE_RESULT, PT_CLASS_IDENTIFIER_TYPE_NODE, - PT_CLASS_LOOSE_COMPARISON_HELPER, PT_CLASS_COMPOUND_TYPE, PT_CLASS_CONSTANT_SCALAR_TYPE, PT_CLASS_GENERIC_TYPE_NODE, diff --git a/turbo-ext/tests/type-family.php b/turbo-ext/tests/type-family.php index 4547a6fbe7f..719c37de245 100644 --- a/turbo-ext/tests/type-family.php +++ b/turbo-ext/tests/type-family.php @@ -670,7 +670,7 @@ public function getValue(): string // ---- FloatType / ConstantFloatType / NullType / VoidType ---- // the string family's reflection provider and PhpVersion accessors stay // registered from the section above (looseCompare() against objects with -// __toString() and LooseComparisonHelper consult them) +// __toString() and ValueSet consult them) $floatPhpVersions = [new \PHPStan\Php\PhpVersion(70400), new \PHPStan\Php\PhpVersion(80400)]; $floatOthers = static fn (string $float, string $constFloat, string $null, string $void): array => [ 'float' => new $float(), @@ -1019,7 +1019,7 @@ public function getValue() 'benevolent' => new \PHPStan\Type\BenevolentUnionType([$floatRangeClass::fromInterval(1.0, 2.0), new \PHPStan\Type\IntegerType()]), 'string' => new \PHPStan\Type\StringType(), ]; - // the loose equality sets of LooseComparisonHelper, reached through OperatorTypeTrait + // the loose equality sets of ValueSet, reached through OperatorTypeTrait $looseEqualityOperands = $arithmeticOperands + [ 'null' => new \PHPStan\Type\NullType(), 'true' => new \PHPStan\Type\Constant\ConstantBooleanType(true), @@ -7966,6 +7966,23 @@ public function getResolvedPhpDoc(): ?\PHPStan\PhpDoc\ResolvedPhpDocBlock { retu } } } + // the numeric-string parsers of StringSet and ValueSet's scalar comparison, at each PHP version's rules + $comparedStrings = ['', ' ', '0', '00', '1', '-1', '+1', '1 ', ' 1', "1\n", "\t\n\r\v\f12\t", '1.', '.5', '+.5', '-.5e3', '1.0', '1e', '1e5', '1E-5', '1.e5', 'abc', '1abc', '.', '-', '0x1A', '1_000', 'INF', '-INF', 'NAN', '9223372036854775807', '9223372036854775808', '-9223372036854775809', '99999999999999999999', '1e1000', '-1e1000']; + $comparedValues = [...$comparedStrings, null, true, false, 0, 1, -1, 0.0, -0.0, 1.5, NAN, INF, -INF, PHP_INT_MAX, PHP_INT_MIN, 9.2233720368547758E18, 1e1000]; + foreach ([70400, 80000, 80500] as $compareVersionId) { + $compareVersion = new \PHPStan\Php\PhpVersion($compareVersionId); + foreach ($comparedStrings as $string) { + $r['parseNumericString ' . $compareVersionId . ' ' . json_encode($string)] = $catching(static fn () => \PHPStan\Type\StringSet::parseNumericString($string, $compareVersion)); + } + foreach ($comparedValues as $leftIndex => $left) { + foreach ($comparedValues as $rightIndex => $right) { + $r["compareScalarValues $compareVersionId $leftIndex $rightIndex"] = \PHPStan\Type\ValueSet::compareScalarValues($left, $right, $compareVersion); + } + } + } + foreach ($comparedStrings as $string) { + $r['parseNumericPrefix ' . json_encode($string)] = $catching(static fn () => \PHPStan\Type\StringSet::parseNumericPrefix($string)); + } foreach ($operands as $name => $operand) { $r["exponentiate $name int7"] = $catching(static fn () => $operand->exponentiate($int(7))); } From b7aa52fe576fd6f00241377e6390f13cebc734d8 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 08:02:17 +0000 Subject: [PATCH 09/15] Raise the memory limit of self-analysis to 512M The main process peaks at 428 MB on 2.3.x and already runs out of the 450M limit on Windows. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 05614c1f875..c6a887d6ca3 100644 --- a/Makefile +++ b/Makefile @@ -163,22 +163,22 @@ cs-fix: cs-install XDEBUG_MODE=off php build-cs/vendor/bin/phpcbf phpstan: - php bin/phpstan clear-result-cache -q && php -d memory_limit=450M bin/phpstan -v + php bin/phpstan clear-result-cache -q && php -d memory_limit=512M bin/phpstan -v phpstan-result-cache: - php -d memory_limit=450M bin/phpstan + php -d memory_limit=512M bin/phpstan phpstan-fix: - php -d memory_limit=450M bin/phpstan --fix + php -d memory_limit=512M bin/phpstan --fix phpstan-generate-baseline: - php -d memory_limit=450M bin/phpstan --generate-baseline + php -d memory_limit=512M bin/phpstan --generate-baseline phpstan-generate-baseline-php: - php -d memory_limit=450M bin/phpstan analyse --generate-baseline phpstan-baseline.php + php -d memory_limit=512M bin/phpstan analyse --generate-baseline phpstan-baseline.php phpstan-pro: - php -d memory_limit=450M bin/phpstan --pro + php -d memory_limit=512M bin/phpstan --pro # Regenerates src/Type/Regex/Parser/RegexParser.php from # build/regex-parser/RegexGrammar.pp3 with the phplrt compiler. The compiler From 3743f7b9edb1e16c84b4012ecc40cd8ada154e3e Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 08:02:25 +0000 Subject: [PATCH 10/15] Do not use the nullsafe operator and the NAN cast to bool in ValueSet The PHP 7.4 downgrade does not transform ?->, and PHP 8.5 warns when casting NAN to bool. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/ValueSet.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php index d8b59404096..6acf71cc525 100644 --- a/src/Type/ValueSet.php +++ b/src/Type/ValueSet.php @@ -262,7 +262,7 @@ public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type foreach ($this->getConstantStrings() as $constantString) { $types[] = self::getConstantStringPartnerType($constantString->getValue(), $phpVersion); } - $stringType = $this->strings?->getType(); + $stringType = $this->strings !== null ? $this->strings->getType() : null; if ($stringType !== null) { $types[] = self::getStringPartnerType($stringType, $phpVersion); } @@ -288,7 +288,7 @@ public function getPossiblyLooselyEqualType(PhpVersion $phpVersion): Type */ public function getAlwaysLooselyEqualType(PhpVersion $phpVersion): Type { - if ($this->unknown || $this->strings?->getType() !== null || $this->nonEmptyArray || $this->resources) { + if ($this->unknown || ($this->strings !== null && $this->strings->getType() !== null) || $this->nonEmptyArray || $this->resources) { return new NeverType(); } @@ -750,7 +750,7 @@ private static function isOnlyNonNegativeIntegers(NumberSet $numbers): bool /** @return list */ private function getConstantStrings(): array { - return $this->strings?->getConstantStrings() ?? []; + return $this->strings !== null ? $this->strings->getConstantStrings() : []; } /** @@ -786,7 +786,7 @@ private function getMembers(): array foreach ($this->getConstantStrings() as $constantString) { $members[] = self::createConstantString($constantString); } - $stringType = $this->strings?->getType(); + $stringType = $this->strings !== null ? $this->strings->getType() : null; if ($stringType !== null) { $members[] = self::createStrings($stringType); } @@ -815,7 +815,7 @@ private function toNumberSetOfNumber(): ?NumberSet } $numbers = $numbers->union($stringNumbers); } - $stringType = $this->strings?->getType(); + $stringType = $this->strings !== null ? $this->strings->getType() : null; if ($stringType !== null) { $stringNumbers = $stringType->toNumber()->toNumberSet(); if ($stringNumbers === null) { @@ -1001,8 +1001,8 @@ private function calculateCommonMath(string $operator, self $right, PhpVersion $ || $leftNumberType instanceof BenevolentUnionType || $rightNumberType instanceof BenevolentUnionType; - $leftNumbers = $this->unknown || $right->unknown ? null : $leftNumberType?->toNumberSet(); - $rightNumbers = $this->unknown || $right->unknown ? null : $rightNumberType?->toNumberSet(); + $leftNumbers = $this->unknown || $right->unknown ? null : ($leftNumberType !== null ? $leftNumberType->toNumberSet() : null); + $rightNumbers = $this->unknown || $right->unknown ? null : ($rightNumberType !== null ? $rightNumberType->toNumberSet() : null); if ($leftNumbers !== null && $rightNumbers !== null) { if ($operator === '+') { $result = $leftNumbers->add($rightNumbers, $phpVersion); @@ -1088,7 +1088,7 @@ private function toNumberType(): ?Type } $otherTypes = $this->getConstantStrings(); - foreach ([$this->strings?->getType(), $this->arrays, $this->objects] as $type) { + foreach ([$this->strings !== null ? $this->strings->getType() : null, $this->arrays, $this->objects] as $type) { if ($type === null) { continue; } @@ -1121,7 +1121,7 @@ private function toNumberType(): ?Type private function isArray(): TrinaryLogic { if ($this->unknown) { - return $this->unknownType?->isArray() ?? TrinaryLogic::createMaybe(); + return $this->unknownType !== null ? $this->unknownType->isArray() : TrinaryLogic::createMaybe(); } if ($this->mayBeArray() && $this->mayBeNonArray()) { return TrinaryLogic::createMaybe(); @@ -1387,7 +1387,7 @@ private function getFiniteScalarTypes(bool $expandIntegerIntervals): ?array { if ( $this->unknown - || $this->strings?->getType() !== null + || ($this->strings !== null && $this->strings->getType() !== null) || $this->emptyArray || $this->nonEmptyArray || $this->objects !== null @@ -1474,7 +1474,7 @@ private function toOperandNumbers(bool $withoutArrays, bool $integers): ?NumberS } $types = $this->getConstantStrings(); - $stringType = $this->strings?->getType(); + $stringType = $this->strings !== null ? $this->strings->getType() : null; if ($stringType !== null) { $types[] = $stringType; } @@ -1650,8 +1650,10 @@ private function getNumberValues(NumberSet $numbers): ?array private static function getTypeLooselyEqualTo(bool|float|int|string|null $value, PhpVersion $phpVersion): Type { // null compares with a string as with '', with anything else by truthiness, like the booleans - $types = [new ConstantBooleanType((bool) $value)]; - if (is_string($value) ? $value === '' : !(bool) $value) { + // not (bool) $value: PHP 8.5 warns when casting NAN, which is truthy + $truthy = !in_array($value, [null, false, 0, 0.0, '', '0'], true); + $types = [new ConstantBooleanType($truthy)]; + if (is_string($value) ? $value === '' : !$truthy) { $types[] = new NullType(); } if ($value === null) { From 3a478a247b77197f4d42d8ce22b19b0746cf2a95 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 08:02:25 +0000 Subject: [PATCH 11/15] Add regression test for a mixed operand of + giving a benevolent union Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- tests/PHPStan/Analyser/nsrt/bug-15388.php | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-15388.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-15388.php b/tests/PHPStan/Analyser/nsrt/bug-15388.php new file mode 100644 index 00000000000..4ef52f1748c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-15388.php @@ -0,0 +1,31 @@ + $vals */ +function foo(array $vals): int { + $result = array_reduce($vals, function($carry, $val) { + assertType('(float|int)', $carry + map($val)); + return $carry + map($val); + }, 0); + assertType('(float|int)', $result); + + return $result; +} + +function map(int $val): int { + return $val; +} + +/** @param list $vals */ +function bar(array $vals): int { + $result = array_reduce($vals, function($carry, $val) { + assertType('(float|int)', $carry + $val); + return $carry + $val; + }, 0); + assertType('(float|int)', $result); + + return $result; +} From e47d1e71c6408964f4e718760ae6a1f26c89594d Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 08:11:14 +0000 Subject: [PATCH 12/15] Compile the native value sets with MSVC Handlers passed to reg::Class::method() need ZEND_FASTCALL, which is __vectorcall on Windows, and MSVC has no __builtin_*_overflow. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- turbo-ext/src/NumberSet.cpp | 25 ++++++++++++++++++++----- turbo-ext/src/StringSet.cpp | 2 +- turbo-ext/src/TypeTraits.cpp | 2 +- turbo-ext/src/TypeTraits.h | 2 +- turbo-ext/src/ValueSet.cpp | 7 ++++--- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/turbo-ext/src/NumberSet.cpp b/turbo-ext/src/NumberSet.cpp index 9bf8bc0e709..2565b3d7f6d 100644 --- a/turbo-ext/src/NumberSet.cpp +++ b/turbo-ext/src/NumberSet.cpp @@ -25,6 +25,21 @@ namespace sigs = ptdecl::NumberSet::sig; static zend_class_entry *pt_ce_number_set = nullptr; +/* $a * $b of two ints: true when PHP would overflow it into a float, like + * StatementContext::multiplyOverflows() (MSVC has no __builtin_mul_overflow) */ +static bool pt_ns_multiply_overflows(zend_long a, zend_long b, zend_long &product) +{ +#if defined(__GNUC__) + return __builtin_mul_overflow(a, b, &product); +#else + double productDouble = 0; + zend_long overflowed = 0; + ZEND_SIGNED_MULTIPLY_LONG(a, b, product, productDouble, overflowed); + (void) productDouble; + return overflowed != 0; +#endif +} + namespace phpstanturbo { /* Mirrors PHPStan\Type\NumberSet. */ @@ -853,12 +868,12 @@ class NumberSetMath zend_long factor = base; while (exponent > 0) { if (exponent % 2 == 1) { - if (__builtin_mul_overflow(result, factor, &result)) return NullableLong::null(); + if (pt_ns_multiply_overflows(result, factor, result)) return NullableLong::null(); exponent--; continue; } exponent /= 2; - if (__builtin_mul_overflow(factor, factor, &factor)) return NullableLong::null(); + if (pt_ns_multiply_overflows(factor, factor, factor)) return NullableLong::null(); } return NullableLong::of(result); @@ -1171,7 +1186,7 @@ bool pt_type_to_number_set(zval *type, NumberSetData &out, bool &has) /* intDivide() & co.: (self $right, PhpVersion $phpVersion) */ template -static void pt_ns_integer_operator(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL pt_ns_integer_operator(INTERNAL_FUNCTION_PARAMETERS) { zval *right, *phpVersion; if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); @@ -1194,7 +1209,7 @@ static void pt_ns_integer_operator(INTERNAL_FUNCTION_PARAMETERS) /* union() / intersect(): (self $other) — PHP's max()/min() keep the first of * equal values, as `b > a ? b : a` does */ template -static void pt_ns_combine(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL pt_ns_combine(INTERNAL_FUNCTION_PARAMETERS) { zval *other; if (!zp::parse(execute_data, other)) RETURN_THROWS(); @@ -1235,7 +1250,7 @@ static void pt_ns_combine(INTERNAL_FUNCTION_PARAMETERS) /* add() & co.: (self $right, PhpVersion $phpVersion) */ template -static void pt_ns_operator(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL pt_ns_operator(INTERNAL_FUNCTION_PARAMETERS) { zval *right, *phpVersion; if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); diff --git a/turbo-ext/src/StringSet.cpp b/turbo-ext/src/StringSet.cpp index 91e0fb9f49c..eb9bc71ed1f 100644 --- a/turbo-ext/src/StringSet.cpp +++ b/turbo-ext/src/StringSet.cpp @@ -657,7 +657,7 @@ static bool requireInstance(zval *value, zend_class_entry *ce, uint32_t position } template -static void pt_ss_combine(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL pt_ss_combine(INTERNAL_FUNCTION_PARAMETERS) { zval *other; if (!zp::parse(execute_data, other)) RETURN_THROWS(); diff --git a/turbo-ext/src/TypeTraits.cpp b/turbo-ext/src/TypeTraits.cpp index a64629da9f3..1388f09f2d5 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -181,7 +181,7 @@ zv::Val pt_value_set_power(zend_object *type, zval *exponent, zval *phpVersion) return pt_type_call(Z_OBJ_P(baseValues.raw()), PT_LC("arithmeticpower"), 2, argv); } -void pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS) +void ZEND_FASTCALL pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS) { ZEND_PARSE_PARAMETERS_NONE(); zv::Val numbers = pt_type_call(Z_OBJ_P(ZEND_THIS), PT_LC("tonumberset"), 0, NULL); diff --git a/turbo-ext/src/TypeTraits.h b/turbo-ext/src/TypeTraits.h index 1566804c518..e29e21d15d4 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -180,7 +180,7 @@ zv::Val pt_value_set_create(const char *lcFactory, size_t len, uint32_t argc = 0 void pt_value_set_of_this(INTERNAL_FUNCTION_PARAMETERS, const char *lcFactory, size_t len); /* ValueSet::createNumbers($this->toNumberSet()) */ -void pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS); +void ZEND_FASTCALL pt_value_set_of_numbers(INTERNAL_FUNCTION_PARAMETERS); /* the union (UnionType) or the intersection (IntersectionType) of the sets of * $this->getTypes() */ diff --git a/turbo-ext/src/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp index 8e94bd93705..01674081734 100644 --- a/turbo-ext/src/ValueSet.cpp +++ b/turbo-ext/src/ValueSet.cpp @@ -924,8 +924,9 @@ class ValueSetOps zend_long min = interval.min.value; zend_long max = interval.max.value; if (min != max) { - zend_long width; - bool overflows = __builtin_sub_overflow(max, min, &width); + // max - min overflows exactly when it exceeds ZEND_LONG_MAX (min < max here) + bool overflows = min < 0 && max > ZEND_LONG_MAX + min; + zend_long width = overflows ? 0 : max - min; // only a range on its own is listed, as IntegerRangeType::getFiniteTypes() lists it if (!expandIntegerIntervals || overflows || width > PT_IETR_CALCULATE_SCALARS_LIMIT || !isOnlyOneIntegerInterval(s)) return true; } @@ -2455,7 +2456,7 @@ static bool requireInstance(zval *value, zend_class_entry *ce, uint32_t position /* (self $right, PhpVersion $phpVersion) operators */ template -static void pt_vs_operator(INTERNAL_FUNCTION_PARAMETERS) +static void ZEND_FASTCALL pt_vs_operator(INTERNAL_FUNCTION_PARAMETERS) { zval *right, *phpVersion; if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); From 8ff417f36bc7c9ccff6d8481750970839c9a4a7b Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 08:38:46 +0000 Subject: [PATCH 13/15] Fold the acceptance of a float range by a union one member at a time AcceptsResult: :or() takes a single operand. Spreading every member into it dropped all but the first in PHP, and the native AcceptsResult::or() rejected the call when Turbo trusts PHPStan's own types. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/FloatRangeType.php | 8 +++++++- turbo-ext/src/FloatRangeType.cpp | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Type/FloatRangeType.php b/src/Type/FloatRangeType.php index c3393c8e0ae..1abfc73b35e 100644 --- a/src/Type/FloatRangeType.php +++ b/src/Type/FloatRangeType.php @@ -284,7 +284,13 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsRes } if ($acceptingType instanceof UnionType) { - return AcceptsResult::createNo()->or(...array_map(fn (Type $innerType): AcceptsResult => $innerType->accepts($this, $strictTypes), $acceptingType->getTypes())); + // AcceptsResult::or() takes a single operand, unlike IsSuperTypeOfResult::or() + $result = AcceptsResult::createNo(); + foreach ($acceptingType->getTypes() as $innerType) { + $result = $result->or($innerType->accepts($this, $strictTypes)); + } + + return $result; } // the other type cannot tell floats apart diff --git a/turbo-ext/src/FloatRangeType.cpp b/turbo-ext/src/FloatRangeType.cpp index b1244cdf921..1a99ef469db 100644 --- a/turbo-ext/src/FloatRangeType.cpp +++ b/turbo-ext/src/FloatRangeType.cpp @@ -385,10 +385,16 @@ class FloatRangeType zv::Args args{&selfZv, &strict}; return pt_type_op(Z_OBJ_P(innerType), PT_OP_ACCEPTS, 2, args); }))) return zv::Val(); - zv::Val no = pt_type_accepts_result(PT_TRI_NO); - if (UNEXPECTED(no.isUndef())) return zv::Val(); + // AcceptsResult::or() takes a single operand, unlike IsSuperTypeOfResult::or() + zv::Val result = pt_type_accepts_result(PT_TRI_NO); + if (UNEXPECTED(result.isUndef())) return zv::Val(); HashTable *table = results.table(); - return pt_type_call(Z_OBJ_P(no.raw()), PT_LC("or"), zend_hash_num_elements(table), table->arPacked); + for (uint32_t i = 0; i < zend_hash_num_elements(table); i++) { + result = pt_type_call(Z_OBJ_P(result.raw()), PT_LC("or"), 1, &table->arPacked[i]); + if (UNEXPECTED(result.isUndef())) return zv::Val(); + } + + return result; } // the other type cannot tell floats apart From cf708c42e719c7afdecd35cf7bc8eb6439f44ee3 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 09:28:45 +0000 Subject: [PATCH 14/15] Do not cast NAN to bool in the native ValueSet zend_is_true() warns on NAN on PHP 8.5, like the (bool) cast in PHP. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- turbo-ext/src/ValueSet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/turbo-ext/src/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp index 01674081734..f347844b9fa 100644 --- a/turbo-ext/src/ValueSet.cpp +++ b/turbo-ext/src/ValueSet.cpp @@ -1927,7 +1927,8 @@ class ValueSetOps { // null compares with a string as with '', with anything else by truthiness, like the booleans zv::Arr types = zv::Arr::create(8); - bool truthy = zend_is_true(value); + // not zend_is_true(): PHP 8.5 warns when casting NAN, which is truthy + bool truthy = Z_TYPE_P(value) == IS_DOUBLE ? Z_DVAL_P(value) != 0.0 : zend_is_true(value); IETR_VAL(booleanType, newConstantBooleanType(truthy)); types.push(std::move(booleanType)); if (Z_TYPE_P(value) == IS_STRING ? Z_STRLEN_P(value) == 0 : !truthy) { From f9d9539d1b9f2229504b0f394f3188b850421a66 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Thu, 5 Nov 2026 09:28:45 +0000 Subject: [PATCH 15/15] Concatenate a benevolent union as the members that can be cast to string A benevolent array|string, such as str_replace() of mixed, concatenates as its strings again, as Type::toString() of a BenevolentUnionType leaves out what cannot be cast. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a1e7-5e72-7235-a4b2-ba1b689f7e1b --- src/Type/ValueSet.php | 24 +++++++++++++++---- .../Analyser/nsrt/concat-benevolent-union.php | 16 +++++++++++++ turbo-ext/src/ValueSet.cpp | 18 +++++++++++--- 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/concat-benevolent-union.php diff --git a/src/Type/ValueSet.php b/src/Type/ValueSet.php index 6acf71cc525..9f5a54343e9 100644 --- a/src/Type/ValueSet.php +++ b/src/Type/ValueSet.php @@ -688,12 +688,22 @@ public function stringConcatenate(self $right, PhpVersion $phpVersion): Type return $leftStrings->concatenate($rightStrings, $phpVersion); } - /** The strings a (string) cast gives for the values, null when it is an error for one of them. */ + /** + * The strings a (string) cast gives for the values, null when it is an error for one of them; + * a benevolent set leaves those out as long as any are left. + */ private function toStringSet(): ?StringSet { - if ($this->invalid || $this->emptyArray || $this->nonEmptyArray) { + if ($this->invalid) { return null; } + $leftOut = false; + if ($this->emptyArray || $this->nonEmptyArray) { + if (!$this->benevolent) { + return null; + } + $leftOut = true; + } if ($this->unknown) { return self::toStringSetOfType(($this->unknownType ?? new MixedType())->toString()); } @@ -714,14 +724,20 @@ private function toStringSet(): ?StringSet } if ($this->objects !== null) { $objectStrings = self::toStringSetOfType($this->objects->toString()); - if ($objectStrings === null) { + if ($objectStrings !== null) { + $strings = $strings->union($objectStrings); + } elseif ($this->benevolent) { + $leftOut = true; + } else { return null; } - $strings = $strings->union($objectStrings); } if ($this->resources) { $strings = $strings->union(StringSet::createType(new StringType())); } + if ($leftOut && $strings->isEmpty()) { + return null; + } return $strings; } diff --git a/tests/PHPStan/Analyser/nsrt/concat-benevolent-union.php b/tests/PHPStan/Analyser/nsrt/concat-benevolent-union.php new file mode 100644 index 00000000000..2af22e8767d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/concat-benevolent-union.php @@ -0,0 +1,16 @@ +slug)); + + /** @var array|string $arrayOrString */ + $arrayOrString = $mixed; + assertType('*ERROR*', 'attribute/' . $arrayOrString); +} diff --git a/turbo-ext/src/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp index f347844b9fa..4f8db4095f8 100644 --- a/turbo-ext/src/ValueSet.cpp +++ b/turbo-ext/src/ValueSet.cpp @@ -1617,7 +1617,13 @@ class ValueSetOps /* Mirrors toStringSet(): a StringSet, or PHP null */ static zv::Val toStringSet(ValueSetView s) { - if (s.invalid() || s.emptyArray() || s.nonEmptyArray()) return zv::Val::null(); + if (s.invalid()) return zv::Val::null(); + // a benevolent set leaves out what cannot be cast, as long as any strings are left + bool leftOut = false; + if (s.emptyArray() || s.nonEmptyArray()) { + if (!s.benevolent()) return zv::Val::null(); + leftOut = true; + } if (s.unknown()) { zv::Val mixed; zval *unknownType = s.unknownType(); @@ -1659,13 +1665,19 @@ class ValueSetOps if (s.objects() != nullptr) { IETR_VAL(objectStringType, callOn(s.objects(), mToString)); IETR_VAL(objectStrings, toStringSetOfType(objectStringType.raw())); - if (Z_TYPE_P(objectStrings.raw()) == IS_NULL) return objectStrings; - if (UNEXPECTED(!unionWith(std::move(objectStrings)))) return zv::Val(); + if (Z_TYPE_P(objectStrings.raw()) != IS_NULL) { + if (UNEXPECTED(!unionWith(std::move(objectStrings)))) return zv::Val(); + } else if (s.benevolent()) { + leftOut = true; + } else { + return objectStrings; + } } if (s.resources()) { IETR_VAL(stringType, newStringType()); if (UNEXPECTED(!unionWith(pt_string_set_create_type(stringType.raw())))) return zv::Val(); } + if (leftOut && pt_string_set_is_empty(strings.raw())) return zv::Val::null(); return strings; }