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 diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index aaf043221ee..14f91cdd161 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,60 +1453,6 @@ parameters: count: 4 path: src/Type/ObjectWithoutClassType.php - - - rawMessage: Binary operation "&" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: Binary operation "*" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: Binary operation "+" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: Binary operation "-" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: Binary operation "^" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: Binary operation "|" between bool|float|int|string|null and bool|float|int|string|null results in an error. - identifier: binaryOp.invalid - count: 1 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' - identifier: phpstanApi.instanceofType - count: 16 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' - identifier: phpstanApi.instanceofType - count: 6 - path: src/Type/OperatorTypeHelper.php - - - - rawMessage: PHPDoc tag @var with type float|int is not subtype of native type int. - identifier: varTag.nativeType - count: 1 - path: src/Type/OperatorTypeHelper.php - - rawMessage: 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/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/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index cca0437a806..5bb4ceb6bb8 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($this); + } + } 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..0ed7c192c61 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($this); + } + } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 115798dfc62..b8173e23d11 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($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 7a07b9d6422..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 @@ -214,4 +214,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createBooleans(true, true); + } + } 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/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..3152bf39d5a 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); + } + } 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/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/src/Type/FloatType.php b/src/Type/FloatType.php index d9f0172303b..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 @@ -361,4 +361,9 @@ public function hasTemplateOrLateResolvableType(): bool return false; } + public function toValueSet(): ValueSet + { + return ValueSet::createNumbers($this->toNumberSet()); + } + } 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/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..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 @@ -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/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/LooseComparisonHelper.php b/src/Type/LooseComparisonHelper.php deleted file mode 100644 index 2ad5ef58ee0..00000000000 --- a/src/Type/LooseComparisonHelper.php +++ /dev/null @@ -1,644 +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+)?)'; - - // a float with a larger magnitude is not exact enough to tell its neighbouring integers apart - private const EXACT_FLOAT_INTEGER_LIMIT = 9007199254740992; - - /** - * 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()) { - $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]; - } - - 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()) { - $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) { - $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); - } - - 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 - * 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; - } - - /** - * 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. - */ - private static function getNumberPartnerType(Type $integerType, float $min, float $max, PhpVersion $phpVersion): Type - { - $types = [$integerType]; - if ($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(); - } - 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'); - } - } - } - - return TypeCombinator::union(...$types); - } - - 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()]); - } - - 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); - } - - /** - * 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 - */ - private static function parseNumericString(string $value, PhpVersion $phpVersion): ?array - { - $pattern = $phpVersion->allowsTrailingWhitespaceInNumericStrings() - ? '/^[' . self::WHITESPACE . ']*' . self::NUMBER . '[' . self::WHITESPACE . ']*$/D' - : '/^[' . self::WHITESPACE . ']*' . self::NUMBER . '$/D'; - if (preg_match($pattern, $value, $matches) !== 1) { - return null; - } - - return self::toNumber($matches); - } - - /** - * The number a string starts with, as PHP 7 compares a string with a number. - */ - private static function parseNumericPrefix(string $value): int|float|null - { - if (preg_match('/^[' . self::WHITESPACE . ']*' . self::NUMBER . '/', $value, $matches) !== 1) { - return null; - } - - return self::toNumber($matches)[0]; - } - - /** - * @param array $matches - * @return array{int|float, int} - */ - private static function toNumber(array $matches): array - { - $numericString = $matches[1]; - if (!is_numeric($numericString)) { - throw new ShouldNotHappenException(); - } - - $number = +$numericString; - $isIntegerSyntax = ($matches[2] ?? '') !== '' && ($matches[3] ?? '') === ''; - if ($isIntegerSyntax && is_float($number)) { - return [$number, $number < 0 ? -1 : 1]; - } - - return [$number, 0]; - } - -} diff --git a/src/Type/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 5d484908cb3..24b715105af 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::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 6c88b5c3b01..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()) { @@ -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 @@ -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..15cac0a9686 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; @@ -77,6 +79,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 { @@ -104,6 +146,148 @@ 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 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 { @@ -219,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; } @@ -379,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. @@ -522,6 +826,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/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/OperatorTypeHelper.php b/src/Type/OperatorTypeHelper.php deleted file mode 100644 index fd7439adc42..00000000000 --- a/src/Type/OperatorTypeHelper.php +++ /dev/null @@ -1,1478 +0,0 @@ -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 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(); - } - - $bitwiseAndRange = self::computeBitwiseAndRange($leftNumberType, $rightNumberType); - if ($bitwiseAndRange !== null) { - return $bitwiseAndRange; - } - - return new IntegerType(); - } - - public static function bitwiseOr(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type - { - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return self::getNeverType($leftType, $rightType); - } - - $result = self::getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a | $b); - if ($result instanceof Type) { - return $result; - } - - // computed before optimizeScalarType() below widens integer ranges to int - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($result === self::IS_SCALAR_TYPE) { - $leftType = self::optimizeScalarType($leftType); - $rightType = self::optimizeScalarType($rightType); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([new IntegerType(), new StringType()]); - } - - $leftIsString = $leftType->isString(); - $rightIsString = $rightType->isString(); - if ( - ($leftIsString->yes() || $leftType instanceof MixedType) - && ($rightIsString->yes() || $rightType instanceof MixedType) - ) { - // 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(); - } - - $bitwiseOrRange = self::computeBitwiseOrXorRange($leftNumberType, $rightNumberType); - if ($bitwiseOrRange !== null) { - return $bitwiseOrRange; - } - - return new IntegerType(); - } - - public static function bitwiseXor(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type - { - if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return self::getNeverType($leftType, $rightType); - } - - $result = self::getFiniteOrConstantScalarTypes($leftType, $rightType, static fn ($a, $b) => $a ^ $b); - if ($result instanceof Type) { - return $result; - } - - // computed before optimizeScalarType() below widens integer ranges to int - $leftNumberType = $leftType->toNumber(); - $rightNumberType = $rightType->toNumber(); - - if ($result === self::IS_SCALAR_TYPE) { - $leftType = self::optimizeScalarType($leftType); - $rightType = self::optimizeScalarType($rightType); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([new IntegerType(), new StringType()]); - } - - $leftIsString = $leftType->isString(); - $rightIsString = $rightType->isString(); - if ( - ($leftIsString->yes() || $leftType instanceof MixedType) - && ($rightIsString->yes() || $rightType instanceof MixedType) - ) { - // 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(); - } - - $bitwiseXorRange = self::computeBitwiseOrXorRange($leftNumberType, $rightNumberType); - if ($bitwiseXorRange !== null) { - return $bitwiseXorRange; - } - - return new IntegerType(); - } - - /** - * @param callable(bool|float|int|string|null, bool|float|int|string|null):string $operationCallable - * - * @return self::IS_UNKNOWN|self::IS_SCALAR_TYPE|Type - */ - private static function getFiniteOrConstantScalarTypes(Type $leftType, Type $rightType, callable $operationCallable): int|Type - { - if ($leftType instanceof IntegerRangeType) { - $leftTypes = $leftType->getFiniteTypes(); - } else { - $leftTypes = $leftType->getConstantScalarTypes(); - } - if ($rightType instanceof IntegerRangeType) { - $rightTypes = $rightType->getFiniteTypes(); - } else { - $rightTypes = $rightType->getConstantScalarTypes(); - } - - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - - if ($leftTypesCount === 0 || $rightTypesCount === 0) { - return self::IS_UNKNOWN; - } - - $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; - if ($generalize) { - return self::IS_SCALAR_TYPE; - } - - $resultTypes = []; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { - $resultValue = $operationCallable($leftTypeInner->getValue(), $rightTypeInner->getValue()); - $resultType = self::getTypeFromValue($resultValue); - } else { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $resultValue = $operationCallable(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(); - } - } - - $maxMagnitude = null; - $divisorBounds = self::getIntegerBounds($rightType->toInteger()); - if ($divisorBounds !== null) { - $maxMagnitude = self::getMaxModuloMagnitude($divisorBounds[0], $divisorBounds[1]); - } - - [$leftMin, $leftMax] = self::getIntegerBounds($leftType->toInteger()) ?? [null, null]; - if ($leftMin === null && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()) { - $leftMin = 0; - } - if ($leftMax === null && IntegerRangeType::fromInterval(null, 0)->isSuperTypeOf($leftType)->yes()) { - $leftMax = 0; - } - - // The result has the sign of the dividend and is never bigger in magnitude than either - // the dividend or the largest magnitude the divisor can have. - if ($leftMax !== null && $leftMax <= 0) { - $rangeMax = 0; - } elseif ($maxMagnitude === null) { - $rangeMax = $leftMax; - } elseif ($leftMax === null) { - $rangeMax = $maxMagnitude; - } else { - $rangeMax = min($leftMax, $maxMagnitude); - } - - if ($leftMin !== null && $leftMin >= 0) { - $rangeMin = 0; - } elseif ($maxMagnitude === null) { - $rangeMin = $leftMin; - } elseif ($leftMin === null) { - $rangeMin = -$maxMagnitude; - } else { - $rangeMin = max($leftMin, -$maxMagnitude); - } - - return IntegerRangeType::fromInterval($rangeMin, $rangeMax); - } - - /** - * Highest possible absolute value of `$x % $divisor`, which is one less than the largest - * possible absolute value of the divisor. Null when there is no such bound. - * - * A divisor reaching PHP_INT_MIN is reported as unbounded too: `abs(PHP_INT_MIN)` does not - * fit into an integer, and the bound it stands for is PHP_INT_MAX, which is all an unbounded - * result can hold anyway. - */ - private static function getMaxModuloMagnitude(?int $divisorMin, ?int $divisorMax): ?int - { - if ($divisorMin === null || $divisorMax === null || $divisorMin === PHP_INT_MIN) { - return null; - } - - $magnitude = max(abs($divisorMin), abs($divisorMax)); - if ($magnitude === 0) { - return null; - } - - return $magnitude - 1; - } - - /** - * @return array{int|null, int|null}|null Lowest and highest value the type can hold, with null - * for an unbounded side. Null when the type 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 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(); - } - - private static function tryArrayUnion(Type $leftType, Type $rightType): ?Type - { - $leftConstantArrays = $leftType->getConstantArrays(); - $rightConstantArrays = $rightType->getConstantArrays(); - - $leftCount = count($leftConstantArrays); - $rightCount = count($rightConstantArrays); - if ($leftCount > 0 && $rightCount > 0 - && ($leftCount + $rightCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT)) { - $resultTypes = []; - foreach ($rightConstantArrays as $rightConstantArray) { - foreach ($leftConstantArrays as $leftConstantArray) { - $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); - foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) { - $optional = $leftConstantArray->isOptionalKey($i); - $valueType = $leftConstantArray->getOffsetValueType($leftKeyType); - if (!$optional) { - if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) { - $valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType)); - } - } - $newArrayBuilder->setOffsetValueType( - $leftKeyType, - $valueType, - $optional, - ); - } - $resultTypes[] = $newArrayBuilder->getArray(); - } - } - return TypeCombinator::union(...$resultTypes); - } - - // `array{...} + array` keeps the known keyed prefix and - // folds the right-hand side into an open `...` tail. This - // only applies when the left arrays' sealedness is known (bleeding edge); - // otherwise the legacy behavior below is kept for backward compatibility. - if ($leftCount > 0 && $rightCount === 0 - && $leftCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT - && $rightType->isArray()->yes()) { - $allSealednessKnown = true; - foreach ($leftConstantArrays as $leftConstantArray) { - if ($leftConstantArray->isUnsealed()->maybe()) { - $allSealednessKnown = false; - break; - } - } - - if ($allSealednessKnown) { - $rightKeyType = $rightType->getIterableKeyType(); - $rightValueType = $rightType->getIterableValueType(); - $resultTypes = []; - foreach ($leftConstantArrays as $leftConstantArray) { - $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($leftConstantArray); - $existingUnsealed = $leftConstantArray->getUnsealedTypes(); - if ($leftConstantArray->isUnsealed()->yes() && $existingUnsealed !== null) { - $newArrayBuilder->makeUnsealed( - TypeCombinator::union($existingUnsealed[0], $rightKeyType), - TypeCombinator::union($existingUnsealed[1], $rightValueType), - ); - } else { - $newArrayBuilder->makeUnsealed($rightKeyType, $rightValueType); - } - $resultTypes[] = $newArrayBuilder->getArray(); - } - return TypeCombinator::union(...$resultTypes); - } - } - - $leftIsArray = $leftType->isArray(); - $rightIsArray = $rightType->isArray(); - if ($leftIsArray->yes() && $rightIsArray->yes()) { - if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { - // to preserve BenevolentUnionType - $keyType = $leftType->getIterableKeyType(); - } else { - $keyTypes = []; - foreach ([ - $leftType->getIterableKeyType(), - $rightType->getIterableKeyType(), - ] as $keyType) { - $keyTypes[] = $keyType; - } - $keyType = TypeCombinator::union(...$keyTypes); - } - - $leftIterableValueType = $leftType->getIterableValueType(); - $arrayType = new ArrayType( - $keyType, - TypeCombinator::union($leftIterableValueType, $rightType->getIterableValueType()), - ); - - $accessories = []; - if ($leftCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - $constantArray = $leftConstantArrays[0]; - foreach ($constantArray->getKeyTypes() as $offsetType) { - if (!$leftType->hasOffsetValueType($offsetType)->yes()) { - continue; - } - - $valueType = $leftType->getOffsetValueType($offsetType); - $accessories[] = new HasOffsetValueType($offsetType, $valueType); - } - } - - if ($rightCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - $constantArray = $rightConstantArrays[0]; - foreach ($constantArray->getKeyTypes() as $offsetType) { - if (!$rightType->hasOffsetValueType($offsetType)->yes()) { - continue; - } - - $valueType = TypeCombinator::union($leftIterableValueType, $rightType->getOffsetValueType($offsetType)); - $accessories[] = new HasOffsetValueType($offsetType, $valueType); - } - } - - if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { - $accessories[] = new NonEmptyArrayType(); - } - if ($leftType->isList()->yes() && $rightType->isList()->yes()) { - $accessories[] = new AccessoryArrayListType(); - } - - if (count($accessories) > 0) { - $arrayType = TypeCombinator::intersect($arrayType, ...$accessories); - } - - return $arrayType; - } - - return null; - } - - public static function subtract(Type $leftType, Type $rightType, PhpVersion $phpVersion): Type - { - $leftTypes = $leftType->getConstantScalarTypes(); - $rightTypes = $rightType->getConstantScalarTypes(); - $leftTypesCount = count($leftTypes); - $rightTypesCount = count($rightTypes); - if ($leftTypesCount > 0 && $rightTypesCount > 0) { - $resultTypes = []; - $generalize = $leftTypesCount * $rightTypesCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT; - if (!$generalize) { - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); - - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } - - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } - - $resultType = self::getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue()); - $resultTypes[] = $resultType; - } - } - - return TypeCombinator::union(...$resultTypes); - } - - $leftType = self::optimizeScalarType($leftType); - $rightType = self::optimizeScalarType($rightType); - } - - return self::resolveCommonMath('-', $leftType, $rightType, $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); - } - - /** - * @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 - */ - /** - * @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; - } - - 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 - * 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 - { - // 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; - } - - $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()); - } - - return $negated; - } - - /** - * The divisor's negative and positive parts are divided separately (dividing by zero throws), - * each at the corners of the operand ranges: truncated division is monotonic in both operands - * as long as the divisor keeps its sign. - */ - public static function intDivide(Type $numeratorType, Type $divisorType, PhpVersion $phpVersion): Type - { - $numeratorIntervals = self::getIntervals($numeratorType->toInteger()); - $divisorIntervals = self::getIntervals($divisorType->toInteger()); - if ($numeratorIntervals === null || $divisorIntervals === null) { - return new ErrorType(); - } - - $resultTypes = []; - foreach ($numeratorIntervals as [$numeratorMin, $numeratorMax]) { - foreach ($divisorIntervals as [$divisorMin, $divisorMax]) { - if ($divisorMin < 0) { - $resultTypes[] = self::divideIntervals($numeratorMin, $numeratorMax, $divisorMin, min($divisorMax, -1)); - } - if ($divisorMax <= 0) { - continue; - } - - $resultTypes[] = self::divideIntervals($numeratorMin, $numeratorMax, max($divisorMin, 1), $divisorMax); - } - } - - if (count($resultTypes) === 0) { - return new ErrorType(); - } - - return TypeCombinator::union(...$resultTypes); - } - - /** - * Unbounded ends are represented by -INF and INF. - * - * @return list|null - */ - private static function getIntervals(Type $type): ?array - { - $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; - } - - return intdiv((int) $numerator, (int) $divisor); - } - -} 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/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/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/StringSet.php b/src/Type/StringSet.php new file mode 100644 index 00000000000..4e03cec114c --- /dev/null +++ b/src/Type/StringSet.php @@ -0,0 +1,411 @@ + $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; + } + + /** + * 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]; + } + + private static function isNumeric(string $value, PhpVersion $phpVersion): bool + { + return self::parseNumericString($value, $phpVersion) !== null; + } + + /** @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/StringType.php b/src/Type/StringType.php index d885c1dd125..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 @@ -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..cb1aff7f754 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); + } + } 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/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..70eed1b6e65 100644 --- a/src/Type/Traits/OperatorTypeTrait.php +++ b/src/Type/Traits/OperatorTypeTrait.php @@ -4,10 +4,17 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; -use PHPStan\Type\LooseComparisonHelper; +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 { @@ -22,84 +29,244 @@ 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); + $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 { - 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 { - return OperatorTypeHelper::concat($this, $right, $phpVersion); + return $this->toValueSet()->stringConcatenate($right->toValueSet(), $phpVersion); } 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/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..9f5a54343e9 --- /dev/null +++ b/src/Type/ValueSet.php @@ -0,0 +1,2004 @@ +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 + { + 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); + } + + 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(ConstantStringType $string): self + { + 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: StringSet::createType($strings)); + } + + /** Arrays of an array type: ArrayType, ConstantArrayType or an accessory such as NonEmptyArrayType. */ + public static function createArrays(Type $arrays): self + { + $isIterableAtLeastOnce = $arrays->isIterableAtLeastOnce(); + + return new self(false, emptyArray: !$isIterableAtLeastOnce->yes(), nonEmptyArray: !$isIterableAtLeastOnce->no(), arrays: $arrays); + } + + public static function createObjects(Type $objects): self + { + return new self(false, objects: $objects); + } + + 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->strings === null + && !$this->emptyArray + && !$this->nonEmptyArray + && $this->objects === null + && !$this->resources; + } + + public function union(self $other): self + { + if ($this->unknown || $other->unknown) { + 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, + ); + } + + 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), + $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), + self::unionTypes($this->objects, $other->objects), + $this->resources || $other->resources, + benevolent: $this->benevolent || $other->benevolent, + never: $this->never ?? $other->never, + ); + } + + public function intersect(self $other): self + { + if ($this->unknown) { + return $other; + } + if ($other->unknown) { + return $this; + } + + $strings = $this->strings === null || $other->strings === null ? null : $this->strings->intersect($other->strings); + + 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), + $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), + $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, + ); + } + + /** + * 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->getConstantStrings() as $constantString) { + $types[] = self::getConstantStringPartnerType($constantString->getValue(), $phpVersion); + } + $stringType = $this->strings !== null ? $this->strings->getType() : null; + if ($stringType !== null) { + $types[] = self::getStringPartnerType($stringType, $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->strings->getType() !== 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->getConstantStrings() as $constantString) { + $types[] = self::getTypeLooselyEqualTo($constantString->getValue(), $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(); + } + + /** + * 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. + */ + 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->getConstantStrings() as $constantString) { + if (!is_numeric($constantString->getValue())) { + 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(), + ]); + } + + /** 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; + * a benevolent set leaves those out as long as any are left. + */ + private function toStringSet(): ?StringSet + { + 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()); + } + + $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) { + $strings = $strings->union($objectStrings); + } elseif ($this->benevolent) { + $leftOut = true; + } else { + return null; + } + } + if ($this->resources) { + $strings = $strings->union(StringSet::createType(new StringType())); + } + if ($leftOut && $strings->isEmpty()) { + return null; + } + + 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 !== null ? $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 + * 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->getConstantStrings() as $constantString) { + $members[] = self::createConstantString($constantString); + } + $stringType = $this->strings !== null ? $this->strings->getType() : null; + if ($stringType !== null) { + $members[] = self::createStrings($stringType); + } + + 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->getConstantStrings() as $constantString) { + $stringNumbers = $constantString->toNumber()->toNumberSet(); + if ($stringNumbers === null) { + return null; + } + $numbers = $numbers->union($stringNumbers); + } + $stringType = $this->strings !== null ? $this->strings->getType() : null; + if ($stringType !== null) { + $stringNumbers = $stringType->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->strings !== null; + $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 !== 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); + } 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 = $this->getConstantStrings(); + foreach ([$this->strings !== null ? $this->strings->getType() : null, $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 !== null ? $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->strings->getType() !== 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->getConstantStrings() as $constantString) { + $types[] = $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 = $this->getConstantStrings(); + $stringType = $this->strings !== null ? $this->strings->getType() : null; + if ($stringType !== null) { + $types[] = $stringType; + } + 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->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->strings !== null + || $this->objects !== null + || $this->resources; + } + + private function mayBeNonNumber(): bool + { + return $this->unknown + || $this->null + || $this->false + || $this->true + || $this->strings !== null + || $this->mayBeArray() + || $this->objects !== null + || $this->resources; + } + + 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 + // 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) { + // 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::compareScalarValues($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 = StringSet::parseNumericString($value, $phpVersion); + if ($number !== null) { + return $number[0]; + } + if (!$phpVersion->castsNumbersToStringsOnLooseComparison()) { + // the number the string starts with + return StringSet::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 = StringSet::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 = StringSet::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/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/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/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; +} 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/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); + } +} 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/AccessoryArrayListType.cpp b/turbo-ext/src/AccessoryArrayListType.cpp index d6d1adda1a6..16d3f2aa661 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); 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/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 2399f19f1a5..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') */ @@ -238,6 +236,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/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/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..14bfcd82d84 100644 --- a/turbo-ext/src/ConstantStringType.cpp +++ b/turbo-ext/src/ConstantStringType.cpp @@ -1336,6 +1336,8 @@ 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, 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/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 diff --git a/turbo-ext/src/FloatType.cpp b/turbo-ext/src/FloatType.cpp index fa4ffd86c02..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') */ @@ -271,6 +269,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/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/IetrSupport.h b/turbo-ext/src/IetrSupport.h index 7ae6d97e218..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. */ @@ -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); \ @@ -672,5 +669,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/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/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..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') */ @@ -250,6 +248,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/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 ab3adfeb9aa..8e2674a75e4 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_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 27dffdf638a..85eb16a6daf 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); 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 d58ecaf4893..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); @@ -408,6 +416,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(); @@ -690,14 +700,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 57350981b07..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. */ @@ -299,8 +314,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); @@ -728,6 +743,382 @@ class NumberSetMath return 1; } + +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 (pt_ns_multiply_overflows(result, factor, result)) return NullableLong::null(); + exponent--; + continue; + } + exponent /= 2; + if (pt_ns_multiply_overflows(factor, factor, factor)) return NullableLong::null(); + } + + return NullableLong::of(result); + } + + /* 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,11 +1133,36 @@ 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); } +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); @@ -768,9 +1184,73 @@ 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 ZEND_FASTCALL 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 +static void ZEND_FASTCALL 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) +static void ZEND_FASTCALL pt_ns_operator(INTERNAL_FUNCTION_PARAMETERS) { zval *right, *phpVersion; if (!zp::parse(execute_data, right, phpVersion)) RETURN_THROWS(); @@ -810,10 +1290,41 @@ 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); 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::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/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/OperatorTypeHelper.cpp b/turbo-ext/src/OperatorTypeHelper.cpp deleted file mode 100644 index a95dfd96a58..00000000000 --- a/turbo-ext/src/OperatorTypeHelper.cpp +++ /dev/null @@ -1,2036 +0,0 @@ -/* - * PHPStanTurbo\OperatorTypeHelper — native implementation of - * PHPStan\Type\OperatorTypeHelper, the operators of Type::arithmetic*(), - * Type::bitwise*(), Type::stringConcatenate() and Type::arrayUnion() that - * OperatorTypeTrait delegates to (pt_type_trait_operator() in - * TypeTraits.cpp calls pt_operator_type_helper_binary() / - * pt_operator_type_helper_negate() directly). - * - * A final class of static methods: every method mirrors its twin, the - * private ones are registered too so the method sets stay identical. The - * arithmetic runs on the engine's own operator functions (add_function(), - * mod_function(), bitwise_and_function(), zend_compare(), ...), so int - * overflow into float, float-to-int truncation and the mixed-type - * comparisons behave exactly like the twin's PHP operators. 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. - */ - -#include "support.h" -#include "generated/OperatorTypeHelper.h" - -#include "zv.h" -#include "IetrSupport.h" -#include "reg.h" - -#include -#include -#include - -zend_class_entry *pt_ce_operator_type_helper = nullptr; - -namespace sigs = ptdecl::OperatorTypeHelper::sig; - -using namespace phpstanturbo::ietr; - -namespace phpstanturbo { - -/* Mirrors PHPStan\Type\OperatorTypeHelper; UNDEF = pending exception. */ -class OperatorTypeHelper -{ -public: - - /* {{{ concat() */ - - /* Mirrors concat(). */ - static zv::Val concat(zval *left, zval *right, 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(); - } - - /* }}} */ - - /* {{{ 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(); - } - - zv::Val range = kind == IETR_BITWISE_AND - ? computeBitwiseAndRange(leftNumberType.raw(), rightNumberType.raw()) - : computeBitwiseOrXorRange(leftNumberType.raw(), rightNumberType.raw()); - if (UNEXPECTED(range.isUndef())) return zv::Val(); - if (Z_TYPE_P(range.raw()) != IS_NULL) return range; - - return newIntegerType(); - } - - /* Mirrors getFiniteOrConstantScalarTypes() with the operation of `kind`: - * 0 = the Type in `result`, PT_IETR_IS_SCALAR_TYPE / PT_IETR_IS_UNKNOWN, - * -1 = pending exception */ - static zend_long getFiniteOrConstantScalarTypes(zval *leftType, zval *rightType, IetrBinaryKind kind, zv::Val &result) - { - pt_ietr_binary_op operation = kind == IETR_BITWISE_AND ? bitwise_and_function : (kind == IETR_BITWISE_OR ? bitwise_or_function : bitwise_xor_function); - return getFiniteOrConstantScalarTypesWith(leftType, rightType, operation, NULL, result); - } - - /* the same with the operation as the engine's operator function or, for - * a PHP caller of the private method, its $operationCallable */ - static zend_long getFiniteOrConstantScalarTypesWith(zval *leftType, zval *rightType, pt_ietr_binary_op operation, zval *operationCallable, zv::Val &result) - { - zv::Val leftTypes = isA(leftType, pt_ce_integer_range_type) ? callOn(leftType, mGetFiniteTypes) : callOn(leftType, mGetConstantScalarTypes); - if (UNEXPECTED(leftTypes.isUndef())) return -1; - zv::Val rightTypes = isA(rightType, pt_ce_integer_range_type) ? callOn(rightType, mGetFiniteTypes) : callOn(rightType, mGetConstantScalarTypes); - if (UNEXPECTED(rightTypes.isUndef())) return -1; - if (UNEXPECTED(!requireArray(leftTypes.raw()) || !requireArray(rightTypes.raw()))) return -1; - - zend_long leftTypesCount = zend_hash_num_elements(Z_ARRVAL_P(leftTypes.raw())); - zend_long rightTypesCount = zend_hash_num_elements(Z_ARRVAL_P(rightTypes.raw())); - - if (leftTypesCount == 0 || rightTypesCount == 0) { - return PT_IETR_IS_UNKNOWN; - } - - bool generalize = leftTypesCount * rightTypesCount > PT_IETR_CALCULATE_SCALARS_LIMIT; - if (generalize) { - return PT_IETR_IS_SCALAR_TYPE; - } - - zend_class_entry *constantScalarCe = pt_class(PT_CLASS_CONSTANT_SCALAR_TYPE); - if (UNEXPECTED(constantScalarCe == NULL)) return -1; - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftTypesCount * rightTypesCount)); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftTypes.raw())) { - zval *leftTypeInner = leftEntry.value().raw(); - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightTypes.raw())) { - zval *rightTypeInner = rightEntry.value().raw(); - zv::Val resultType; - if (isA(leftTypeInner, pt_ce_constant_string_type) && isA(rightTypeInner, pt_ce_constant_string_type)) { - zv::Val leftValue = callOn(leftTypeInner, mGetValue); - if (UNEXPECTED(leftValue.isUndef())) return -1; - zv::Val rightValue = callOn(rightTypeInner, mGetValue); - if (UNEXPECTED(rightValue.isUndef())) return -1; - zv::Val resultValue = applyOperation(operation, operationCallable, leftValue.raw(), rightValue.raw()); - if (UNEXPECTED(resultValue.isUndef())) return -1; - resultType = typeFromValue(resultValue.raw()); - } else { - zv::Val leftNumberType = callOn(leftTypeInner, mToNumber); - if (UNEXPECTED(leftNumberType.isUndef())) return -1; - zv::Val rightNumberType = callOn(rightTypeInner, mToNumber); - if (UNEXPECTED(rightNumberType.isUndef())) return -1; - - if (isA(leftNumberType.raw(), pt_ce_error_type) || isA(rightNumberType.raw(), pt_ce_error_type)) { - result = newErrorType(); - return result.isUndef() ? -1 : 0; - } - - if (!isA(leftNumberType.raw(), constantScalarCe) || !isA(rightNumberType.raw(), constantScalarCe)) { - pt_throw_should_not_happen(); - return -1; - } - - 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) - { - zend_long leftIsInteger = triOn(left, mIsInteger); - if (UNEXPECTED(leftIsInteger < 0)) return -1; - if (leftIsInteger == PT_TRI_YES) return 0; - zend_long leftIsFloat = triOn(left, mIsFloat); - if (UNEXPECTED(leftIsFloat < 0)) return -1; - if (leftIsFloat == PT_TRI_YES) return 0; - zend_long leftIsDecimal = triOn(leftStringType, mIsDecimalIntegerString); - if (UNEXPECTED(leftIsDecimal < 0)) return -1; - if (leftIsDecimal == PT_TRI_YES) return 0; - - zv::Val constantStrings = callOn(leftStringType, mGetConstantStrings); - if (UNEXPECTED(constantStrings.isUndef()) || UNEXPECTED(!requireArray(constantStrings.raw()))) return -1; - if (zend_hash_num_elements(Z_ARRVAL_P(constantStrings.raw())) > 0) { - for (zv::ArrayEntry entry : zv::ArrRef(constantStrings.raw())) { - zv::Val value = constantStringValue(entry.value().raw()); - if (UNEXPECTED(value.isUndef())) return -1; - if (UNEXPECTED(Z_TYPE_P(value.raw()) != IS_STRING)) { - pt_throw_should_not_happen(); - return -1; - } - /* Strings::match($value, '#\s\z#'): PCRE's \s */ - size_t length = Z_STRLEN_P(value.raw()); - if (length > 0) { - char last = Z_STRVAL_P(value.raw())[length - 1]; - if (last == ' ' || last == '\t' || last == '\n' || last == '\v' || last == '\f' || last == '\r') return 1; - } - } - return 0; - } - - bool allows; - if (UNEXPECTED(!pt_php_version_answer(phpVersion, PT_PHP_VERSION_ALLOWS_TRAILING_WHITESPACE_IN_NUMERIC_STRINGS, allows))) return -1; - return allows ? 1 : 0; - } - - /* $operationCallable($a, $b) or the operator */ - static zv::Val applyOperation(pt_ietr_binary_op operation, zval *operationCallable, zval *a, zval *b) - { - 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(); - } - } - } - - NullableLong maxMagnitude = NullableLong::null(); - { - IETR_VAL(rightInteger, callOn(rightType, mToInteger)); - bool hasDivisorBounds; - NullableLong divisorMin, divisorMax; - if (UNEXPECTED(!getIntegerBounds(rightInteger.raw(), hasDivisorBounds, divisorMin, divisorMax))) return zv::Val(); - if (hasDivisorBounds) { - maxMagnitude = getMaxModuloMagnitude(divisorMin, divisorMax); - } - } - - bool hasLeftBounds; - NullableLong leftMin, leftMax; - { - IETR_VAL(leftInteger, callOn(leftType, mToInteger)); - if (UNEXPECTED(!getIntegerBounds(leftInteger.raw(), hasLeftBounds, leftMin, leftMax))) return zv::Val(); - } - if (!hasLeftBounds) { - leftMin = NullableLong::null(); - leftMax = NullableLong::null(); - } - if (leftMin.isNull) { - IETR_VAL(nonNegative, fromInterval(NullableLong::of(0), NullableLong::null())); - IETR_TRI(leftIsNonNegative, triOn(nonNegative.raw(), mIsSuperTypeOf, 1, leftType)); - if (leftIsNonNegative == PT_TRI_YES) leftMin = NullableLong::of(0); - } - if (leftMax.isNull) { - IETR_VAL(nonPositive, fromInterval(NullableLong::null(), NullableLong::of(0))); - IETR_TRI(leftIsNonPositive, triOn(nonPositive.raw(), mIsSuperTypeOf, 1, leftType)); - if (leftIsNonPositive == PT_TRI_YES) leftMax = NullableLong::of(0); - } - - // The result has the sign of the dividend and is never bigger in magnitude than either - // the dividend or the largest magnitude the divisor can have. - NullableLong rangeMax; - if (!leftMax.isNull && leftMax.value <= 0) { - rangeMax = NullableLong::of(0); - } else if (maxMagnitude.isNull) { - rangeMax = leftMax; - } else if (leftMax.isNull) { - rangeMax = maxMagnitude; - } else { - rangeMax = NullableLong::of(std::min(leftMax.value, maxMagnitude.value)); - } - - NullableLong rangeMin; - if (!leftMin.isNull && leftMin.value >= 0) { - rangeMin = NullableLong::of(0); - } else if (maxMagnitude.isNull) { - rangeMin = leftMin; - } else if (leftMin.isNull) { - rangeMin = NullableLong::of(-maxMagnitude.value); - } else { - rangeMin = NullableLong::of(std::max(leftMin.value, -maxMagnitude.value)); - } - - return fromInterval(rangeMin, rangeMax); - } - - /* 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; - } - } - - outMin = unboundedMin ? NullableLong::null() : min; - outMax = unboundedMax ? NullableLong::null() : max; - return true; - } - - /* Mirrors getIntegerIntervals(): the integer intervals of a type that - * holds integers and nothing else, has = false for its null; false = - * pending exception */ - [[nodiscard]] static bool getIntegerIntervals(zval *type, std::vector &intervals, bool &has) - { - NumberSetData numberSet; - if (UNEXPECTED(!pt_type_to_number_set(type, numberSet, has))) return false; - if (!has) return true; - - if (!numberSet.floatIntervals.empty() || numberSet.nan || numberSet.integerIntervals.empty()) { - has = false; - return true; - } - - intervals = std::move(numberSet.integerIntervals); - return true; - } - - /* getIntegerIntervals() as the private method returns it: a list of - * [min, max] pairs or null; UNDEF = pending exception */ - static zv::Val getIntegerIntervalsValue(zval *type) - { - std::vector intervals; - bool has = false; - if (UNEXPECTED(!getIntegerIntervals(type, intervals, has))) return zv::Val(); - if (!has) return zv::Val::null(); - return integerIntervalsValue(intervals); - } - - static zv::Val integerIntervalsValue(const std::vector &intervals) - { - zv::Arr list = zv::Arr::create((uint32_t) intervals.size()); - for (const NumberSetData::IntegerInterval &interval : intervals) { - list.push(integerIntervalValue(interval)); - } - return zv::Val(std::move(list)); - } - - static zv::Val integerIntervalValue(const NumberSetData::IntegerInterval &interval) - { - zv::Arr pair = zv::Arr::create(2); - pair.push(interval.min.toVal()); - pair.push(interval.max.toVal()); - return zv::Val(std::move(pair)); - } - - /* getIntegerBounds() as the private method returns it: the [min, max] - * pair or null; UNDEF = pending exception */ - static zv::Val getIntegerBoundsValue(zval *type) - { - 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) - { - 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); - } - - /* Mirrors arrayUnion(). */ - static zv::Val arrayUnion(zval *leftType, zval *rightType) - { - IETR_VAL(result, tryArrayUnion(leftType, rightType)); - if (Z_TYPE_P(result.raw()) != IS_NULL) return result; - return newErrorType(); - } - - /* Mirrors tryArrayUnion(): a Type, or PHP null */ - static zv::Val tryArrayUnion(zval *leftType, zval *rightType) - { - IETR_VAL(leftConstantArrays, callOn(leftType, mGetConstantArrays)); - IETR_VAL(rightConstantArrays, callOn(rightType, mGetConstantArrays)); - if (UNEXPECTED(!requireArray(leftConstantArrays.raw()) || !requireArray(rightConstantArrays.raw()))) return zv::Val(); - - zend_long leftCount = zend_hash_num_elements(Z_ARRVAL_P(leftConstantArrays.raw())); - zend_long rightCount = zend_hash_num_elements(Z_ARRVAL_P(rightConstantArrays.raw())); - if (leftCount > 0 && rightCount > 0 - && (leftCount + rightCount < PT_CONSTANT_ARRAY_TYPE_BUILDER_ARRAY_COUNT_LIMIT)) { - zv::Arr resultTypes = zv::Arr::create((uint32_t) (leftCount * rightCount)); - for (zv::ArrayEntry rightEntry : zv::ArrRef(rightConstantArrays.raw())) { - zval *rightConstantArray = rightEntry.value().raw(); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { - zval *leftConstantArray = leftEntry.value().raw(); - IETR_VAL(newArrayBuilder, pt_constant_array_type_builder_create_from_constant_array(rightConstantArray)); - IETR_VAL(leftKeyTypes, callOn(leftConstantArray, mGetKeyTypes)); - if (UNEXPECTED(!requireArray(leftKeyTypes.raw()))) return zv::Val(); - for (zv::ArrayEntry keyEntry : zv::ArrRef(leftKeyTypes.raw())) { - zval *leftKeyType = keyEntry.value().raw(); - zval i; - entryKey(keyEntry, i); - IETR_VAL(optionalHold, callOn(leftConstantArray, mIsOptionalKey, 1, &i)); - bool optional = zend_is_true(optionalHold.raw()); - IETR_VAL(valueType, callOn(leftConstantArray, mGetOffsetValueType, 1, leftKeyType)); - zv::Val valueTypeHold = std::move(valueType); - if (!optional) { - IETR_TRI(rightHas, triOn(rightConstantArray, mHasOffsetValueType, 1, leftKeyType)); - if (rightHas == PT_TRI_MAYBE) { - IETR_VAL(rightValueType, callOn(rightConstantArray, mGetOffsetValueType, 1, leftKeyType)); - IETR_VAL(merged, union2(valueTypeHold.raw(), rightValueType.raw())); - valueTypeHold = std::move(merged); - } - } - if (UNEXPECTED(!pt_constant_array_type_builder_set_offset_value_type(newArrayBuilder.raw(), leftKeyType, valueTypeHold.raw(), optional))) return zv::Val(); - } - IETR_VAL(array, pt_constant_array_type_builder_get_array(newArrayBuilder.raw())); - resultTypes.push(std::move(array)); - } - } - return unionOf(resultTypes); - } - - // `array{...} + array` keeps the known keyed prefix and - // folds the right-hand side into an open `...` tail. This - // only applies when the left arrays' sealedness is known (bleeding edge); - // otherwise the legacy behavior below is kept for backward compatibility. - if (leftCount > 0 && rightCount == 0 - && leftCount < PT_CONSTANT_ARRAY_TYPE_BUILDER_ARRAY_COUNT_LIMIT) { - IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); - if (rightIsArray == PT_TRI_YES) { - bool allSealednessKnown = true; - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { - IETR_TRI(unsealed, triOn(leftEntry.value().raw(), mIsUnsealed)); - if (unsealed == PT_TRI_MAYBE) { - allSealednessKnown = false; - break; - } - } - - if (allSealednessKnown) { - IETR_VAL(rightKeyType, callOn(rightType, mGetIterableKeyType)); - IETR_VAL(rightValueType, callOn(rightType, mGetIterableValueType)); - zv::Arr resultTypes = zv::Arr::create((uint32_t) leftCount); - for (zv::ArrayEntry leftEntry : zv::ArrRef(leftConstantArrays.raw())) { - zval *leftConstantArray = leftEntry.value().raw(); - IETR_VAL(newArrayBuilder, pt_constant_array_type_builder_create_from_constant_array(leftConstantArray)); - IETR_VAL(existingUnsealed, callOn(leftConstantArray, mGetUnsealedTypes)); - IETR_TRI(unsealed, triOn(leftConstantArray, mIsUnsealed)); - if (unsealed == PT_TRI_YES && Z_TYPE_P(existingUnsealed.raw()) != IS_NULL) { - zval *existingKey = arrayIndex(existingUnsealed.raw(), 0); - if (UNEXPECTED(existingKey == NULL)) return zv::Val(); - IETR_VAL(keyUnion, union2(existingKey, rightKeyType.raw())); - zval *existingValue = arrayIndex(existingUnsealed.raw(), 1); - if (UNEXPECTED(existingValue == NULL)) return zv::Val(); - IETR_VAL(valueUnion, union2(existingValue, rightValueType.raw())); - if (UNEXPECTED(!pt_constant_array_type_builder_make_unsealed(newArrayBuilder.raw(), keyUnion.raw(), valueUnion.raw()))) return zv::Val(); - } else { - if (UNEXPECTED(!pt_constant_array_type_builder_make_unsealed(newArrayBuilder.raw(), rightKeyType.raw(), rightValueType.raw()))) return zv::Val(); - } - IETR_VAL(array, pt_constant_array_type_builder_get_array(newArrayBuilder.raw())); - resultTypes.push(std::move(array)); - } - return unionOf(resultTypes); - } - } - } - - IETR_TRI(leftIsArray, triOn(leftType, mIsArray)); - IETR_TRI(rightIsArray, triOn(rightType, mIsArray)); - if (leftIsArray == PT_TRI_YES && rightIsArray == PT_TRI_YES) { - IETR_VAL(leftKeyTypeForEquals, callOn(leftType, mGetIterableKeyType)); - IETR_VAL(rightKeyTypeForEquals, callOn(rightType, mGetIterableKeyType)); - zv::Val keyType; - { - zv::Val equalsHold = callOn(leftKeyTypeForEquals.raw(), mEquals, 1, rightKeyTypeForEquals.raw()); - if (UNEXPECTED(equalsHold.isUndef())) return zv::Val(); - if (zend_is_true(equalsHold.raw())) { - // to preserve BenevolentUnionType - keyType = callOn(leftType, mGetIterableKeyType); - } else { - IETR_VAL(leftKey, callOn(leftType, mGetIterableKeyType)); - IETR_VAL(rightKey, callOn(rightType, mGetIterableKeyType)); - keyType = union2(leftKey.raw(), rightKey.raw()); - } - if (UNEXPECTED(keyType.isUndef())) return zv::Val(); - } - - IETR_VAL(leftIterableValueType, callOn(leftType, mGetIterableValueType)); - IETR_VAL(rightIterableValueType, callOn(rightType, mGetIterableValueType)); - IETR_VAL(valueUnion, union2(leftIterableValueType.raw(), rightIterableValueType.raw())); - IETR_VAL(arrayType, newArrayType(keyType.raw(), valueUnion.raw())); - - zv::Arr accessories = zv::Arr::create(4); - if (leftCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - zval *constantArray = arrayIndex(leftConstantArrays.raw(), 0); - if (UNEXPECTED(constantArray == NULL)) return zv::Val(); - IETR_VAL(keyTypes, callOn(constantArray, mGetKeyTypes)); - if (UNEXPECTED(!requireArray(keyTypes.raw()))) return zv::Val(); - for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { - zval *offsetType = keyEntry.value().raw(); - IETR_TRI(hasOffset, triOn(leftType, mHasOffsetValueType, 1, offsetType)); - if (hasOffset != PT_TRI_YES) { - continue; - } - - IETR_VAL(valueType, callOn(leftType, mGetOffsetValueType, 1, offsetType)); - zval accessory; - if (UNEXPECTED(!pt_has_offset_value_type_new(&accessory, offsetType, valueType.raw()))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - } - - if (rightCount > 0) { - // Use the first constant array as a reference to list potential offsets. - // We only need to check the first array because we're looking for offsets that exist in ALL arrays. - zval *constantArray = arrayIndex(rightConstantArrays.raw(), 0); - if (UNEXPECTED(constantArray == NULL)) return zv::Val(); - IETR_VAL(keyTypes, callOn(constantArray, mGetKeyTypes)); - if (UNEXPECTED(!requireArray(keyTypes.raw()))) return zv::Val(); - for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { - zval *offsetType = keyEntry.value().raw(); - IETR_TRI(hasOffset, triOn(rightType, mHasOffsetValueType, 1, offsetType)); - if (hasOffset != PT_TRI_YES) { - continue; - } - - IETR_VAL(rightOffsetValueType, callOn(rightType, mGetOffsetValueType, 1, offsetType)); - IETR_VAL(valueType, union2(leftIterableValueType.raw(), rightOffsetValueType.raw())); - zval accessory; - if (UNEXPECTED(!pt_has_offset_value_type_new(&accessory, offsetType, valueType.raw()))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - } - - IETR_TRI(leftAtLeastOnce, triOn(leftType, mIsIterableAtLeastOnce)); - bool nonEmpty = leftAtLeastOnce == PT_TRI_YES; - if (!nonEmpty) { - IETR_TRI(rightAtLeastOnce, triOn(rightType, mIsIterableAtLeastOnce)); - nonEmpty = rightAtLeastOnce == PT_TRI_YES; - } - if (nonEmpty) { - zval accessory; - if (UNEXPECTED(!pt_non_empty_array_type_new(&accessory))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - IETR_TRI(leftIsList, triOn(leftType, mIsList)); - if (leftIsList == PT_TRI_YES) { - IETR_TRI(rightIsList, triOn(rightType, mIsList)); - if (rightIsList == PT_TRI_YES) { - zval accessory; - if (UNEXPECTED(!pt_accessory_array_list_type_new(&accessory))) return zv::Val(); - accessories.push(zv::Val::adopt(accessory)); - } - } - - uint32_t accessoryCount = zend_hash_num_elements(accessories.table()); - if (accessoryCount > 0) { - zv::Arr intersectArgs = zv::Arr::create(accessoryCount + 1); - intersectArgs.push(std::move(arrayType)); - for (zv::ArrayEntry entry : zv::ArrRef(accessories.raw())) { - intersectArgs.push(entry.value()); - } - return pt_type_combinator_intersect(accessoryCount + 1, intersectArgs.table()->arPacked); - } - - return arrayType; - } - - return zv::Val::null(); - } - - /* Mirrors subtract() / multiply() (kind IETR_MINUS / IETR_MUL). */ - static zv::Val subtractOrMultiply(IetrBinaryKind kind, zval *leftType, zval *rightType, 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); - } - - /* 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() */ - - /* 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 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; - } - - 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); - } - - 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() */ - - /* an interval bound of intDivide(): an int, or the twin's -INF / INF - * (inf -1 / 1) for an unbounded end */ - struct Bound - { - int inf; - zend_long value; - - static Bound of(zend_long value) { return { 0, value }; } - static Bound infinity(int sign) { return { sign, 0 }; } - - bool lessThan(const Bound &other) const - { - if (inf != other.inf) return inf < other.inf; - return inf == 0 && value < other.value; - } - - /* the int|float the twin's private methods trade */ - void toZval(zval *out) const - { - if (inf != 0) { - ZVAL_DOUBLE(out, inf * std::numeric_limits::infinity()); - } else { - ZVAL_LONG(out, value); - } - } - - /* an int|float parameter of the twin's private methods; false = - * TypeError thrown */ - [[nodiscard]] static bool fromZval(zval *value, uint32_t arg, Bound &out) - { - if (Z_TYPE_P(value) == IS_LONG) { - out = of(Z_LVAL_P(value)); - return true; - } - if (Z_TYPE_P(value) == IS_DOUBLE && std::isinf(Z_DVAL_P(value))) { - out = infinity(Z_DVAL_P(value) > 0 ? 1 : -1); - return true; - } - if (Z_TYPE_P(value) == IS_DOUBLE && Z_DVAL_P(value) == std::trunc(Z_DVAL_P(value)) && Z_DVAL_P(value) >= (double) ZEND_LONG_MIN && Z_DVAL_P(value) < (double) ZEND_LONG_MAX) { - out = of((zend_long) Z_DVAL_P(value)); - return true; - } - zend_argument_type_error(arg, "must be of type int|float, %s given", zend_zval_value_name(value)); - return false; - } - }; - - struct Interval - { - Bound min; - Bound max; - }; - - /* Mirrors intDivide(). */ - static zv::Val intDivide(zval *numeratorType, zval *divisorType) - { - std::vector numeratorIntervals; - std::vector divisorIntervals; - bool numeratorKnown, divisorKnown; - { - IETR_VAL(numeratorInteger, callOn(numeratorType, mToInteger)); - if (UNEXPECTED(!getIntervals(numeratorInteger.raw(), numeratorIntervals, numeratorKnown))) return zv::Val(); - IETR_VAL(divisorInteger, callOn(divisorType, mToInteger)); - if (UNEXPECTED(!getIntervals(divisorInteger.raw(), divisorIntervals, divisorKnown))) return zv::Val(); - } - if (!numeratorKnown || !divisorKnown) { - return newErrorType(); - } - - zv::Arr resultTypes = zv::Arr::create((uint32_t) (numeratorIntervals.size() * divisorIntervals.size() * 2)); - for (const Interval &numerator : numeratorIntervals) { - for (const Interval &divisor : divisorIntervals) { - if (divisor.min.lessThan(Bound::of(0))) { - Bound minusOne = Bound::of(-1); - IETR_VAL(negative, divideIntervals(numerator.min, numerator.max, divisor.min, minusOne.lessThan(divisor.max) ? minusOne : divisor.max)); - resultTypes.push(std::move(negative)); - } - if (!Bound::of(0).lessThan(divisor.max)) { - continue; - } - - Bound one = Bound::of(1); - IETR_VAL(positive, divideIntervals(numerator.min, numerator.max, divisor.min.lessThan(one) ? one : divisor.min, divisor.max)); - resultTypes.push(std::move(positive)); - } - } - - if (zend_hash_num_elements(resultTypes.table()) == 0) { - return newErrorType(); - } - - return unionOf(resultTypes); - } - - /* Mirrors getIntervals(): `known` false for the twin's null; false = - * pending exception */ - [[nodiscard]] static bool getIntervals(zval *type, std::vector &intervals, bool &known) - { - known = false; - std::vector integerIntervals; - bool has = false; - if (UNEXPECTED(!getIntegerIntervals(type, integerIntervals, has))) return false; - if (!has) { - zend_long isInteger = triOn(type, mIsInteger); - if (UNEXPECTED(isInteger < 0)) return false; - if (isInteger != PT_TRI_YES) return true; - intervals.push_back({ Bound::infinity(-1), Bound::infinity(1) }); - known = 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; - return true; - } - - /* getIntervals() as the private method returns it: a list of [min, max] - * pairs or null; UNDEF = pending exception */ - static zv::Val getIntervalsValue(zval *type) - { - std::vector intervals; - bool known; - if (UNEXPECTED(!getIntervals(type, intervals, known))) return zv::Val(); - if (!known) return zv::Val::null(); - zv::Arr list = zv::Arr::create((uint32_t) intervals.size()); - for (const Interval &interval : intervals) { - zval min, max; - interval.min.toZval(&min); - interval.max.toZval(&max); - zv::Arr pair = zv::Arr::create(2); - pair.push(zv::Val::adopt(min)); - pair.push(zv::Val::adopt(max)); - list.push(zv::Val(std::move(pair))); - } - return zv::Val(std::move(list)); - } - - /* Mirrors divideIntervals(). */ - static zv::Val divideIntervals(Bound numeratorMin, Bound numeratorMax, Bound divisorMin, Bound divisorMax) - { - Bound results[4] = { - divideBounds(numeratorMin, divisorMin), - divideBounds(numeratorMin, divisorMax), - divideBounds(numeratorMax, divisorMin), - divideBounds(numeratorMax, divisorMax), - }; - Bound min = results[0]; - Bound max = results[0]; - for (const Bound &result : results) { - if (result.lessThan(min)) min = result; - if (max.lessThan(result)) max = result; - } - - return fromInterval( - min.inf != 0 ? NullableLong::null() : NullableLong::of(min.value), - max.inf != 0 ? NullableLong::null() : NullableLong::of(max.value) - ); - } - - /* Mirrors divideBounds(). */ - static Bound divideBounds(Bound numerator, Bound divisor) - { - if (numerator.inf != 0) { - if (divisor.inf != 0) { - // the range already contains 0 through a neighbouring corner - return Bound::of(0); - } - - return (numerator.inf > 0) == (divisor.value > 0) ? Bound::infinity(1) : Bound::infinity(-1); - } - - if (divisor.inf != 0) { - return Bound::of(0); - } - - if (numerator.value == ZEND_LONG_MIN && divisor.value == -1) { - // throws ArithmeticError, the closest valid result is intdiv(PHP_INT_MIN + 1, -1) - return Bound::of(ZEND_LONG_MAX); - } - - return Bound::of(numerator.value / divisor.value); - } - - /* }}} */ - -}; - -} // namespace phpstanturbo - -using phpstanturbo::OperatorTypeHelper; -using phpstanturbo::NullableLong; - -/* {{{ direct entries */ - -zv::Val pt_operator_type_helper_binary(pt_operator_type_op op, zval *left, zval *right, zval *phpVersion) -{ - 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: - return OperatorTypeHelper::arrayUnion(left, right); - } - pt_throw_should_not_happen(); - 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 */ - -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 */ -bool otOperatorKind(zend_string *operator_, IetrBinaryKind &kind) -{ - static constexpr struct - { - const char *operator_; - IetrBinaryKind kind; - } operators[] = { - { "+", IETR_PLUS }, - { "-", IETR_MINUS }, - { "*", IETR_MUL }, - { "/", IETR_DIV }, - { "<<", IETR_SHIFT_LEFT }, - { ">>", IETR_SHIFT_RIGHT }, - }; - for (const auto &candidate : operators) { - if (zend_string_equals_cstr(operator_, candidate.operator_, strlen(candidate.operator_))) { - kind = candidate.kind; - return true; - } - } - kind = IETR_OTHER; - return true; -} - -/* the (Type $left, Type $right, PhpVersion $phpVersion) glue */ -#define OT_BINARY_GLUE(body) \ - [](INTERNAL_FUNCTION_PARAMETERS) { \ - zval *leftType, *rightType, *phpVersion; \ - if (!zp::parse(execute_data, leftType, rightType, phpVersion)) RETURN_THROWS(); \ - if (UNEXPECTED(!otArgType(leftType, 1) || !otArgType(rightType, 2) || !otArgPhpVersion(phpVersion, 3))) RETURN_THROWS(); \ - PT_RETURN_VAL(body); \ - } - -/* the (Type $leftType, Type $rightType) glue */ -#define OT_TYPES_GLUE(body) \ - [](INTERNAL_FUNCTION_PARAMETERS) { \ - zval *leftType, *rightType; \ - if (!zp::parse(execute_data, leftType, rightType)) RETURN_THROWS(); \ - if (UNEXPECTED(!otArgType(leftType, 1) || !otArgType(rightType, 2))) RETURN_THROWS(); \ - PT_RETURN_VAL(body); \ - } - -/* the (Type $type) glue */ -#define OT_TYPE_GLUE(body) \ - [](INTERNAL_FUNCTION_PARAMETERS) { \ - zval *type; \ - if (!zp::parse(execute_data, type)) RETURN_THROWS(); \ - if (UNEXPECTED(!otArgType(type, 1))) RETURN_THROWS(); \ - PT_RETURN_VAL(body); \ - } - -} // namespace - -PT_MINIT_REGISTRATION(pt_register_operator_type_helper) -{ - reg::Class cls("PHPStan\\Type\\OperatorTypeHelper"); - ptdecl::OperatorTypeHelper::declareClass(cls); - cls.privateClassConstantLong("IS_SCALAR_TYPE", PT_IETR_IS_SCALAR_TYPE); - cls.privateClassConstantLong("IS_UNKNOWN", PT_IETR_IS_UNKNOWN); - ptdecl::OperatorTypeHelper::declareProperties(cls); - - cls.method(sigs::concat, OT_BINARY_GLUE(OperatorTypeHelper::concat(leftType, rightType, 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::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::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(); - if (UNEXPECTED(!otArgType(left, 1) || !otArgType(leftStringType, 2) || !otArgPhpVersion(phpVersion, 3))) RETURN_THROWS(); - int result = OperatorTypeHelper::canEndInWhitespace(left, leftStringType, phpVersion); - if (UNEXPECTED(result < 0)) RETURN_THROWS(); - RETURN_BOOL(result == 1); - }); - cls.method(sigs::getNonNegativeIntegerBounds, OT_TYPE_GLUE(OperatorTypeHelper::getNonNegativeIntegerBoundsValue(type))); - cls.method(sigs::computeBitwiseAndRange, OT_TYPES_GLUE(OperatorTypeHelper::computeBitwiseAndRange(leftType, rightType))); - cls.method(sigs::computeBitwiseOrXorRange, OT_TYPES_GLUE(OperatorTypeHelper::computeBitwiseOrXorRange(leftType, rightType))); - - cls.method(sigs::allBitsMask, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_long value; - if (!zp::parse(execute_data, value)) RETURN_THROWS(); - RETURN_LONG(OperatorTypeHelper::allBitsMask(value)); - }); - - cls.method(sigs::resolveCommonMath, [](INTERNAL_FUNCTION_PARAMETERS) { - zend_string *operator_; - zval *leftType, *rightType, *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::negateNumber, OT_TYPE_GLUE(OperatorTypeHelper::negateNumber(type))); - - 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.method(sigs::getIntervals, OT_TYPE_GLUE(OperatorTypeHelper::getIntervalsValue(type))); - - cls.method(sigs::divideIntervals, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *numeratorMin, *numeratorMax, *divisorMin, *divisorMax; - if (!zp::parse(execute_data, numeratorMin, numeratorMax, divisorMin, divisorMax)) RETURN_THROWS(); - OperatorTypeHelper::Bound bounds[4]; - zval *values[4] = { numeratorMin, numeratorMax, divisorMin, divisorMax }; - for (uint32_t i = 0; i < 4; i++) { - if (UNEXPECTED(!OperatorTypeHelper::Bound::fromZval(values[i], i + 1, bounds[i]))) RETURN_THROWS(); - } - PT_RETURN_VAL(OperatorTypeHelper::divideIntervals(bounds[0], bounds[1], bounds[2], bounds[3])); - }); - - cls.method(sigs::divideBounds, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *numerator, *divisor; - if (!zp::parse(execute_data, numerator, divisor)) RETURN_THROWS(); - OperatorTypeHelper::Bound numeratorBound, divisorBound; - if (UNEXPECTED(!OperatorTypeHelper::Bound::fromZval(numerator, 1, numeratorBound) || !OperatorTypeHelper::Bound::fromZval(divisor, 2, divisorBound))) RETURN_THROWS(); - OperatorTypeHelper::divideBounds(numeratorBound, divisorBound).toZval(return_value); - }); - - cls.shadow(&pt_ce_operator_type_helper); -} - -#undef OT_BINARY_GLUE -#undef OT_TYPES_GLUE -#undef OT_TYPE_GLUE - -/* }}} */ diff --git a/turbo-ext/src/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/OversizedArrayType.cpp b/turbo-ext/src/OversizedArrayType.cpp index 6002aee9578..aa82b7b4781 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); 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/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/StringSet.cpp b/turbo-ext/src/StringSet.cpp new file mode 100644 index 00000000000..eb9bc71ed1f --- /dev/null +++ b/turbo-ext/src/StringSet.cpp @@ -0,0 +1,749 @@ +/* + * 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\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) + { + 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 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) + { + 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; + } + 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) + { + 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(); +} + +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 */ + +#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 ZEND_FASTCALL 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); + cls.privateClassConstantString("NUMBER", StringSetOps::NUMBER); + /* "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::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(); + 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/StringType.cpp b/turbo-ext/src/StringType.cpp index efb8d06e9fb..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') */ @@ -418,6 +416,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..1388f09f2d5 100644 --- a/turbo-ext/src/TypeTraits.cpp +++ b/turbo-ext/src/TypeTraits.cpp @@ -165,6 +165,49 @@ 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); } +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)); +} + +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 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); + 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); @@ -1032,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); @@ -1088,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); } @@ -2271,6 +2323,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) */ + 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); @@ -3729,6 +3783,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); }); @@ -5434,13 +5489,33 @@ 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) +/* $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(); - PT_RETURN_VAL(pt_operator_type_helper_binary(Op, ZEND_THIS, right, phpVersion)); + 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) @@ -5461,37 +5536,112 @@ void pt_type_trait_operator(reg::Class &cls) ZEND_PARSE_PARAMETERS_NONE(); RETURN_NULL(); }); - cls.traitMethod(sigs::arithmeticAdd, operatorBinary); - cls.traitMethod(sigs::arithmeticSubtract, operatorBinary); - cls.traitMethod(sigs::arithmeticMultiply, operatorBinary); - cls.traitMethod(sigs::arithmeticDivide, operatorBinary); - cls.traitMethod(sigs::arithmeticIntDivide, operatorBinary); - cls.traitMethod(sigs::arithmeticModulo, operatorBinary); + cls.traitMethod(sigs::toValueSet, PT_VALUE_SET_CREATE("createunknown")); + /* 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, 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, PT_OPERATOR_VALUE_SET("stringconcatenate")); + /* 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)); }); - cls.traitMethod(sigs::bitwiseAnd, operatorBinary); - cls.traitMethod(sigs::bitwiseOr, operatorBinary); - cls.traitMethod(sigs::bitwiseXor, operatorBinary); - cls.traitMethod(sigs::bitwiseShiftLeft, operatorBinary); - cls.traitMethod(sigs::bitwiseShiftRight, operatorBinary); - cls.traitMethod(sigs::stringConcatenate, operatorBinary); - cls.traitMethod(sigs::arrayUnion, operatorBinary); - /* 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..e29e21d15d4 100644 --- a/turbo-ext/src/TypeTraits.h +++ b/turbo-ext/src/TypeTraits.h @@ -170,6 +170,42 @@ 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.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); + +/* 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 ZEND_FASTCALL 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); + +/* $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)); } + +#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 PT_VALUE_SET_OF_THIS("createarrays") + +/* }}} */ + /* $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 @@ -305,6 +341,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 @@ -314,6 +352,26 @@ 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); +/* 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 * exception */ @@ -1069,9 +1127,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/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/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/ValueSet.cpp b/turbo-ext/src/ValueSet.cpp new file mode 100644 index 00000000000..4f8db4095f8 --- /dev/null +++ b/turbo-ext/src/ValueSet.cpp @@ -0,0 +1,2661 @@ +/* + * 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 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. + * + * The twin's private methods live only here, in ValueSetOps, method for + * method. The numeric-string parsers are StringSet's (StringSet.cpp). + */ + +#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 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); } + /* the StringSet, or nullptr */ + zval *strings() const { return objectOrNull(slots::strings); } + /* $this->strings?->getConstantStrings() ?? [], borrowed ConstantStringType objects */ + HashTable *constantStrings() const + { + 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 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::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(zval *string) + { + ValueSetFields f; + 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 = pt_string_set_create_type(strings); + if (UNEXPECTED(f.strings.isUndef())) return zv::Val(); + 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.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.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.strings() != nullptr + || s.objects() != nullptr + || s.resources(); + } + + static bool mayBeNonNumber(ValueSetView s) + { + return s.unknown() + || s.null_() + || s.false_() + || s.true_() + || 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 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); + } + + 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(); + } + 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()); + 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); + + 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(); + } + 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()); + 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) { + bool hasNumbers; + if (UNEXPECTED(!convertedNumbers(entry, mToNumber, out, hasNumbers))) return false; + if (!hasNumbers) return true; + } ZEND_HASH_FOREACH_END(); + if (s.stringType() != nullptr) { + bool hasNumbers; + if (UNEXPECTED(!convertedNumbers(s.stringType(), 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) { + types.push_back(zv::Val::copyOf(zv::Ref(entry))); + } ZEND_HASH_FOREACH_END(); + 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; + 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.stringType() != 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) { + // 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; + } + 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) { + types.push_back(zv::Val::copyOf(zv::Ref(entry))); + } 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.strings() != nullptr; + 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) { + otherTypes.push_back(zv::Val::copyOf(zv::Ref(entry))); + } ZEND_HASH_FOREACH_END(); + for (zval *type : {s.stringType(), 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(entry); + if (UNEXPECTED(member.isUndef())) return false; + members.push_back(std::move(member)); + } ZEND_HASH_FOREACH_END(); + if (s.stringType() != nullptr) { + zv::Val member = createStrings(s.stringType()); + 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) { + IETR_VAL(value, constantStringValue(entry)); + if (!isNumeric(Z_STR_P(value.raw()))) 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); + } + + /* {{{ 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()) 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(); + 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) { + 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; + } + + /* 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)) { + 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 */ + + /* {{{ 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) + { + 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) + { + 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)); + + 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 + 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 + 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); + // 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) { + 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) { + 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)); + } + + 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)); + + 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 (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) { + 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()) { + 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(value, constantStringValue(entry)); + IETR_VAL(partner, getConstantStringPartnerType(value.raw(), phpVersion)); + types.push(std::move(partner)); + } ZEND_HASH_FOREACH_END(); + if (s.stringType() != nullptr) { + IETR_VAL(partner, getStringPartnerType(s.stringType(), 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.stringType() != 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(value, constantStringValue(entry)); + IETR_VAL(type, getTypeLooselyEqualTo(value.raw(), 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; + +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); +} + +/* {{{ 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 ZEND_FASTCALL 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 fifteen 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) { + 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; + 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.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/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/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/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/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/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/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/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/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/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/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/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/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..31f14be0690 100644 --- a/turbo-ext/src/generated/NumberSet.h +++ b/turbo-ext/src/generated/NumberSet.h @@ -39,43 +39,72 @@ 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 */ + "intDivide\0" /* 226 */ + "modulo\0" /* 236 */ + "bitwiseAnd\0" /* 243 */ + "bitwiseOr\0" /* 254 */ + "bitwiseXor\0" /* 264 */ + "negate\0" /* 275 */ + "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 */ @@ -83,65 +112,117 @@ 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), /* 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(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(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 */ 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(393, MAY_BE_ARRAY), /* integerToFloatInterval $interval */ reg::packed(48, MAY_BE_ARRAY), /* integerToFloatInterval return */ - reg::packed(300, MAY_BE_ARRAY), /* splitIntegerDivisor $interval */ + reg::packed(393, 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(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(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(352, MAY_BE_ARRAY), /* divideIntegers $left */ - reg::packed(94, MAY_BE_ARRAY), /* divideIntegers $right */ - reg::packed(123, 0, 134), /* divideIntegers $phpVersion */ + 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(421, MAY_BE_LONG | MAY_BE_DOUBLE), /* divideBound $numerator */ - reg::packed(431, 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(375, 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(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(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(595, MAY_BE_LONG), /* allBitsMask $value */ + reg::packed(48, MAY_BE_LONG), /* allBitsMask return */ + reg::packed(48, MAY_BE_ARRAY), /* getIntegerHull return */ + 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(641, MAY_BE_ARRAY), /* intDivideIntervals $numerator */ + reg::packed(651, MAY_BE_ARRAY), /* intDivideIntervals $divisor */ + reg::packed(48, 0, 179), /* intDivideIntervals return */ + 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(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(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(493, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $bound */ - reg::packed(499, MAY_BE_DOUBLE), /* getFloatBoundWithInnerNeighbour $min */ - reg::packed(503, 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; @@ -153,28 +234,48 @@ 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 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 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/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/OperatorTypeHelper.h b/turbo-ext/src/generated/OperatorTypeHelper.h deleted file mode 100644 index aa6c878ed7f..00000000000 --- a/turbo-ext/src/generated/OperatorTypeHelper.h +++ /dev/null @@ -1,241 +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[] = - "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 */ - "bitwiseAnd\0" /* 124 */ - "bitwiseOr\0" /* 135 */ - "bitwiseXor\0" /* 145 */ - "operationCallable\0" /* 156 */ - "getFiniteOrConstantScalarTypes\0" /* 174 */ - "divide\0" /* 205 */ - "getDivTypeFromTypes\0" /* 212 */ - "modulo\0" /* 232 */ - "divisorMin\0" /* 239 */ - "divisorMax\0" /* 250 */ - "getMaxModuloMagnitude\0" /* 261 */ - "type\0" /* 283 */ - "getIntegerBounds\0" /* 288 */ - "getIntegerIntervals\0" /* 305 */ - "add\0" /* 325 */ - "arrayUnion\0" /* 329 */ - "tryArrayUnion\0" /* 340 */ - "subtract\0" /* 354 */ - "multiply\0" /* 363 */ - "shiftLeft\0" /* 372 */ - "shiftRight\0" /* 382 */ - "optimizeScalarType\0" /* 393 */ - "getNonNegativeIntegerBounds\0" /* 412 */ - "leftNumberType\0" /* 440 */ - "rightNumberType\0" /* 455 */ - "computeBitwiseAndRange\0" /* 471 */ - "computeBitwiseOrXorRange\0" /* 494 */ - "value\0" /* 519 */ - "allBitsMask\0" /* 525 */ - "operator\0" /* 537 */ - "resolveCommonMath\0" /* 546 */ - "negateNumber\0" /* 564 */ - "PHPStan\\Type\\ConstantScalarType\0" /* 577 */ - "toIntegerOperand\0" /* 609 */ - "getTypeFromValue\0" /* 626 */ - "getNeverType\0" /* 643 */ - "negate\0" /* 656 */ - "numeratorType\0" /* 663 */ - "divisorType\0" /* 677 */ - "intDivide\0" /* 689 */ - "getIntervals\0" /* 699 */ - "numeratorMin\0" /* 712 */ - "numeratorMax\0" /* 725 */ - "divideIntervals\0" /* 738 */ - "numerator\0" /* 754 */ - "divisor\0" /* 764 */ - "divideBounds"; /* 772 */ -inline constexpr reg::PackedArg args[] = { - reg::packed(0, 0, 5), /* concat $left */ - reg::packed(23, 0, 5), /* concat $right */ - 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), /* 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, 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(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 */ - 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(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(283, 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(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(63, MAY_BE_LONG), /* toIntegerOperand return */ - reg::packed(519, 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(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(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 */ -}; -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 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 getMaxModuloMagnitude = { { 261 /* getMaxModuloMagnitude */, 2, 36, 2, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerBounds = { { 288 /* getIntegerBounds */, 1, 39, 1, 40, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntegerIntervals = { { 305 /* getIntegerIntervals */, 1, 41, 1, 42, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig add = { { 325 /* add */, 3, 43, 3, 46, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig arrayUnion = { { 329 /* arrayUnion */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig tryArrayUnion = { { 340 /* tryArrayUnion */, 2, 51, 2, 53, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig subtract = { { 354 /* subtract */, 3, 54, 3, 57, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig multiply = { { 363 /* multiply */, 3, 58, 3, 61, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftLeft = { { 372 /* shiftLeft */, 3, 62, 3, 65, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig shiftRight = { { 382 /* shiftRight */, 3, 66, 3, 69, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig optimizeScalarType = { { 393 /* optimizeScalarType */, 1, 70, 1, 71, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNonNegativeIntegerBounds = { { 412 /* getNonNegativeIntegerBounds */, 1, 72, 1, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseAndRange = { { 471 /* computeBitwiseAndRange */, 2, 74, 2, 76, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig computeBitwiseOrXorRange = { { 494 /* computeBitwiseOrXorRange */, 2, 77, 2, 79, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig allBitsMask = { { 525 /* allBitsMask */, 1, 80, 1, 81, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolveCommonMath = { { 546 /* resolveCommonMath */, 4, 82, 4, 86, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negateNumber = { { 564 /* negateNumber */, 1, 87, 1, 88, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig toIntegerOperand = { { 609 /* toIntegerOperand */, 1, 89, 1, 90, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getTypeFromValue = { { 626 /* getTypeFromValue */, 1, 91, 1, 92, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNeverType = { { 643 /* getNeverType */, 2, 93, 2, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig negate = { { 656 /* negate */, 2, 96, 2, 98, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intDivide = { { 689 /* intDivide */, 3, 99, 3, 102, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getIntervals = { { 699 /* getIntervals */, 1, 103, 1, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideIntervals = { { 738 /* divideIntervals */, 4, 105, 4, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig divideBounds = { { 772 /* divideBounds */, 2, 110, 2, 112, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -} // namespace sig - -} // namespace ptdecl::OperatorTypeHelper - -#endif 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/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/StringSet.h b/turbo-ext/src/generated/StringSet.h new file mode 100644 index 00000000000..ecf10f1347d --- /dev/null +++ b/turbo-ext/src/generated/StringSet.h @@ -0,0 +1,167 @@ +/* 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 */ + "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 */ + 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), /* 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 */ + 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 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 + +#endif 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/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/ValueSet.h b/turbo-ext/src/generated/ValueSet.h new file mode 100644 index 00000000000..1d578c3b7b5 --- /dev/null +++ b/turbo-ext/src/generated/ValueSet.h @@ -0,0 +1,536 @@ +/* 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 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) +{ + 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("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"); + 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 */ + "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 */ + "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 */ + 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_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(254, 0, 255), /* createBooleans return */ + reg::packed(24, 0, 32), /* createNumbers $numbers */ + 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, 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(652, 0, 255), /* arithmeticSubtract $right */ + reg::packed(559, 0, 570), /* arithmeticSubtract $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticSubtract return */ + reg::packed(652, 0, 255), /* arithmeticMultiply $right */ + reg::packed(559, 0, 570), /* arithmeticMultiply $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticMultiply return */ + reg::packed(652, 0, 255), /* arithmeticDivide $right */ + reg::packed(559, 0, 570), /* arithmeticDivide $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticDivide return */ + 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(652, 0, 255), /* arithmeticModulo $right */ + reg::packed(559, 0, 570), /* arithmeticModulo $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticModulo return */ + reg::packed(860, 0, 255), /* arithmeticPower $exponent */ + reg::packed(559, 0, 570), /* arithmeticPower $phpVersion */ + reg::packed(254, 0, 118), /* arithmeticPower return */ + reg::packed(860, 0, 255), /* powerMember $exponent */ + reg::packed(559, 0, 570), /* powerMember $phpVersion */ + reg::packed(254, 0, 118), /* powerMember return */ + 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 */ + 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(254, MAY_BE_BOOL), /* isOnlyFloats return */ + reg::packed(24, 0, 32), /* isOnlyFloatNumbers $numbers */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyFloatNumbers return */ + reg::packed(24, 0, 32), /* isOnlyInteger $numbers */ + 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(652, 0, 255), /* bitwiseAnd $right */ + reg::packed(559, 0, 570), /* bitwiseAnd $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseAnd return */ + reg::packed(652, 0, 255), /* bitwiseOr $right */ + reg::packed(559, 0, 570), /* bitwiseOr $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseOr return */ + reg::packed(652, 0, 255), /* bitwiseXor $right */ + reg::packed(559, 0, 570), /* bitwiseXor $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseXor return */ + reg::packed(652, 0, 255), /* bitwiseShiftLeft $right */ + reg::packed(559, 0, 570), /* bitwiseShiftLeft $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseShiftLeft return */ + reg::packed(652, 0, 255), /* bitwiseShiftRight $right */ + reg::packed(559, 0, 570), /* bitwiseShiftRight $phpVersion */ + reg::packed(254, 0, 118), /* bitwiseShiftRight return */ + 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(652, 0, 255), /* generalizeTooManyConstants $right */ + reg::packed(254, MAY_BE_ARRAY), /* generalizeTooManyConstants return */ + reg::packed(254, 0, 255), /* generalize return */ + 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(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, 1280), /* isArray return */ + reg::packed(254, MAY_BE_BOOL), /* isOnlyNonEmptyArrays return */ + 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(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(652, 0, 255), /* toResult $right */ + reg::packed(1387, 0, 118), /* toResult $result */ + reg::packed(254, 0, 118), /* toResult return */ + 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(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(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(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(1470, 0, 118), /* getConstantNumber $numberType */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG | MAY_BE_DOUBLE), /* getConstantNumber return */ + reg::packed(1499, 0, 118), /* getConstantInteger $integerType */ + reg::packed(254, MAY_BE_NULL | MAY_BE_LONG), /* getConstantInteger return */ + reg::packed(1530, MAY_BE_BOOL), /* getFiniteScalarTypes $expandIntegerIntervals */ + reg::packed(254, MAY_BE_NULL | MAY_BE_ARRAY), /* getFiniteScalarTypes return */ + 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 */ + 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(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(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(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(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 */ + reg::packed(254, 0, 118), /* getNumberPartnerType return */ + reg::packed(24, 0, 32), /* canBeZero $numbers */ + 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(1852, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeZero */ + reg::packed(1862, MAY_BE_BOOL), /* getBooleanAndNullPartnerType $canBeNonZero */ + reg::packed(254, 0, 118), /* getBooleanAndNullPartnerType return */ + 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(1954, MAY_BE_BOOL), /* getArrayPartnerType $empty */ + reg::packed(1960, MAY_BE_BOOL), /* getArrayPartnerType $nonEmpty */ + reg::packed(254, 0, 118), /* getArrayPartnerType return */ + reg::packed(1808, MAY_BE_LONG | MAY_BE_DOUBLE), /* createNumberType $number */ + reg::packed(254, 0, 118), /* createNumberType return */ + 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 */ + 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 = { { 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 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 + +#endif 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 e34c9b87288..e1dd7bc052a 100644 --- a/turbo-ext/src/support.cpp +++ b/turbo-ext/src/support.cpp @@ -51,8 +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_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 c1cf08062be..d105b6105e7 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -137,8 +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_EXPONENTIATE_HELPER, PT_CLASS_COMPOUND_TYPE, PT_CLASS_CONSTANT_SCALAR_TYPE, PT_CLASS_GENERIC_TYPE_NODE, @@ -4090,36 +4088,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 binary operators of the Type methods OperatorTypeTrait delegates */ -enum pt_operator_type_op : uint8_t -{ - PT_OPERATOR_ADD, - PT_OPERATOR_SUBTRACT, - PT_OPERATOR_MULTIPLY, - PT_OPERATOR_DIVIDE, - PT_OPERATOR_INT_DIVIDE, - PT_OPERATOR_MODULO, - PT_OPERATOR_BITWISE_AND, - PT_OPERATOR_BITWISE_OR, - PT_OPERATOR_BITWISE_XOR, - PT_OPERATOR_SHIFT_LEFT, - PT_OPERATOR_SHIFT_RIGHT, - PT_OPERATOR_CONCATENATE, - PT_OPERATOR_ARRAY_UNION, -}; -/* OperatorTypeHelper::add() / subtract() / ... / concat() / arrayUnion() - * ($left, $right, $phpVersion) and ::negate($type, $phpVersion) (all +/* $left->arrayUnion($right, $phpVersion) of OperatorTypeTrait (both * 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); +zv::Val pt_operator_type_trait_array_union(zval *left, zval *right); /* }}} */ diff --git a/turbo-ext/tests/initializer-escape.php b/turbo-ext/tests/initializer-escape.php index e0366bcb3ba..c707d004409 100644 --- a/turbo-ext/tests/initializer-escape.php +++ b/turbo-ext/tests/initializer-escape.php @@ -109,11 +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 + // 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'))), + || ($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) { diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index c8985a3dfef..30c7f71d7db 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -1119,7 +1119,8 @@ 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 // 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\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 56e6a690784..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(), @@ -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), @@ -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), @@ -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, 'add'))->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); $r = []; @@ -7904,64 +7905,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) { @@ -7975,6 +7922,67 @@ 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))); + } + } + // 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))); + } + } + } + // 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))); }