diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 7d770b8e505..2345376754c 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -23,17 +23,10 @@ permissions: jobs: baseline: - name: "Baseline (PHP ${{ matrix.php-version }})" + name: "Baseline" runs-on: ubuntu-latest timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - php-version: - - "7.4" - - "8.5" - steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 @@ -47,65 +40,31 @@ jobs: uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # 2.37.2 with: coverage: "none" - php-version: "${{ matrix.php-version }}" + php-version: "8.5" tools: pecl extensions: ds,mbstring ini-file: development ini-values: memory_limit=-1 - - name: "Downgrade PHPUnit" - if: matrix.php-version == '7.4' - run: "composer require --dev phpunit/phpunit:^9.6 sebastian/diff:^4.0 doctrine/instantiator:^1.0 --update-with-dependencies --ignore-platform-reqs" - - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 - - # tests/composer.json pins the Composer platform to PHP 8.2, which would make - # Composer resolve PHPBench's Symfony dependencies to versions that cannot - # even be parsed on PHP 7.4. Unsetting it resolves against the real PHP version. - - name: "Downgrade PHPBench" - if: matrix.php-version == '7.4' - run: | - composer config --unset platform.php --working-dir=tests - composer require --dev phpbench/phpbench:1.2.14 phpunit/phpunit:^9.6 brianium/paratest:^6.5 --update-with-all-dependencies --working-dir=tests - - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 - if: matrix.php-version != '7.4' with: working-directory: "tests/" - - uses: ./.github/actions/downgrade-code - with: - php-version: "${{ matrix.php-version }}" - - name: "Run phpbench baseline" - run: "tests/vendor/bin/phpbench run --dump-file=tests/bench/storage/baseline-${{ matrix.php-version }}.xml --ansi" + run: "tests/vendor/bin/phpbench run --dump-file=tests/bench/storage/baseline.xml --ansi" - # Before committing an artifact as the new baseline, compare the - # values (md5, file_rw) in it with the ones in the currently committed baseline. - # GitHub runners differ in speed by up to 2x, and a baseline recorded on a fast - # runner makes every later run look like a regression. - name: "Upload baseline artifact" uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: phpbench-baseline-${{ matrix.php-version }} - path: tests/bench/storage/baseline-${{ matrix.php-version }}.xml + name: phpbench-baseline + path: tests/bench/storage/baseline.xml test: - name: "Test (PHP ${{ matrix.php-version }})" + name: "Test" runs-on: ubuntu-latest timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - include: - # phpbench 1.2.14 is the last version supporting PHP 7.4 - # and it does not have the aggregate-preview report 'my-report' extends - - php-version: "7.4" - report: "aggregate" - - php-version: "8.5" - report: "my-report" - steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 @@ -119,42 +78,16 @@ jobs: uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # 2.37.2 with: coverage: "none" - php-version: "${{ matrix.php-version }}" + php-version: "8.5" tools: pecl extensions: ds,mbstring ini-file: development ini-values: memory_limit=-1 - - name: "Downgrade PHPUnit" - if: matrix.php-version == '7.4' - run: "composer require --dev phpunit/phpunit:^9.6 sebastian/diff:^4.0 doctrine/instantiator:^1.0 --update-with-dependencies --ignore-platform-reqs" - - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 - - # tests/composer.json pins the Composer platform to PHP 8.2, which would make - # Composer resolve PHPBench's Symfony dependencies to versions that cannot - # even be parsed on PHP 7.4. Unsetting it resolves against the real PHP version. - - name: "Downgrade PHPBench" - if: matrix.php-version == '7.4' - run: | - composer config --unset platform.php --working-dir=tests - composer require --dev phpbench/phpbench:1.2.14 phpunit/phpunit:^9.6 brianium/paratest:^6.5 --update-with-all-dependencies --working-dir=tests - - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 - if: matrix.php-version != '7.4' with: working-directory: "tests/" - - uses: ./.github/actions/downgrade-code - with: - php-version: "${{ matrix.php-version }}" - - # PHPBench does not fail when the baseline file is missing, it just runs - # with an empty baseline and every assertion passes - - name: "Check that the baseline exists" - run: | - test -f tests/bench/storage/baseline-${{ matrix.php-version }}.xml \ - || (echo "Missing tests/bench/storage/baseline-${{ matrix.php-version }}.xml, download it from the phpbench-baseline-${{ matrix.php-version }} artifact of the Baseline job and commit it." && exit 1) - - name: "Run phpbench test" - run: "tests/vendor/bin/phpbench run --file=tests/bench/storage/baseline-${{ matrix.php-version }}.xml --report=${{ matrix.report }} --ansi" + run: "tests/vendor/bin/phpbench run --file=tests/bench/storage/baseline.xml --report=my-report --ansi" diff --git a/Makefile b/Makefile index a49c12e6af3..ea78ec2e2cb 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ lint: XDEBUG_MODE=off php vendor/bin/parallel-lint --colors \ --exclude tests/PHPStan/Analyser/data \ --exclude tests/bench/data \ + --exclude tests/bench/RegressionBench.php \ --exclude tests/PHPStan/Analyser/nsrt \ --exclude tests/PHPStan/Rules/Methods/data \ --exclude tests/PHPStan/Rules/Functions/data \ diff --git a/src/Analyser/ClosureProcessor.php b/src/Analyser/ClosureProcessor.php index 88d632b1e99..f1b2f89a9e2 100644 --- a/src/Analyser/ClosureProcessor.php +++ b/src/Analyser/ClosureProcessor.php @@ -434,6 +434,60 @@ public function processByRefInvocation( $enter = fn (MutatingScope $byRefSource): MutatingScope => $this->enterWithCapturedUses($scope, $expr, $callableParameters, $creationScope)->processClosureScope($byRefSource, null, $byRefUses); $entryScope = $enter($scope); + $frame = $scope->getCurrentTemplateArgumentFrame(); + $walk = null; + // the body entered in a state it was already walked from, in the same + // pass: a closure invoked many times mostly re-enters one of a few states + foreach ($storage->findByRefInvocationWalks($expr) as $storedWalk) { + [$walkFrame, $walkUntilFixpoint, $walkNativeTypesPromoted, $walkEntryScope] = $storedWalk; + if ( + $walkFrame !== $frame + || $walkUntilFixpoint !== $untilFixpoint + || $walkNativeTypesPromoted !== $scope->nativeTypesPromoted + || !$walkEntryScope->equals($entryScope) + ) { + continue; + } + $walk = $storedWalk; + break; + } + if ($walk === null) { + [$exitScope, $bodyThrowPoints] = $this->walkByRefInvocation($nodeScopeResolver, $expr, $scope, $storage, $entryScope, $enter, $byRefUses, $untilFixpoint); + $walk = [$frame, $untilFixpoint, $scope->nativeTypesPromoted, $entryScope, $exitScope, $bodyThrowPoints]; + $storage->storeByRefInvocationWalk($expr, $walk); + } + [, , , , $exitScope, $bodyThrowPoints] = $walk; + + $throwPoints = []; + foreach ($bodyThrowPoints as $throwPoint) { + $throwScope = $this->assignByRefUses($scope, $throwPoint->getScope(), $byRefUses); + $throwPoints[] = $throwPoint->isExplicit() + ? InternalThrowPoint::createExplicit($throwScope, $throwPoint->getType(), $call, $throwPoint->canContainAnyThrowable()) + : InternalThrowPoint::createImplicit($throwScope, $call); + } + + return [$this->assignByRefUses($scope, $exitScope, $byRefUses), $throwPoints]; + } + + /** + * The walks of processByRefInvocation() from the entry scope: the body's + * exit scope and throw points, in the closure's own scope. + * + * @param callable(MutatingScope): MutatingScope $enter + * @param Node\ClosureUse[] $byRefUses + * @return array{MutatingScope, list} + */ + private function walkByRefInvocation( + NodeScopeResolver $nodeScopeResolver, + Expr\Closure $expr, + MutatingScope $scope, + ExpressionResultStorage $storage, + MutatingScope $entryScope, + callable $enter, + array $byRefUses, + bool $untilFixpoint, + ): array + { $exitScope = null; $throwPoints = []; $count = 0; @@ -445,10 +499,7 @@ public function processByRefInvocation( } $exitScope = $exitScope === null ? $passExitScope : $exitScope->mergeWith($passExitScope); foreach ($result->getThrowPoints() as $throwPoint) { - $throwScope = $this->assignByRefUses($scope, $throwPoint->getScope(), $byRefUses); - $throwPoints[] = $throwPoint->isExplicit() - ? InternalThrowPoint::createExplicit($throwScope, $throwPoint->getType(), $call, $throwPoint->canContainAnyThrowable()) - : InternalThrowPoint::createImplicit($throwScope, $call); + $throwPoints[] = $throwPoint; } if (!$untilFixpoint) { break; @@ -471,7 +522,7 @@ public function processByRefInvocation( $exitScope = $exitScope->mergeWith($entryScope); } - return [$this->assignByRefUses($scope, $exitScope, $byRefUses), $throwPoints]; + return [$exitScope, $throwPoints]; } /** diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index 9e3b11c2d3a..de7b6c6ea89 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -225,11 +225,8 @@ private function collectAbsorbedItems(Array_ $expr, array $itemResults, Mutating } $arrayType = $this->initializerExprTypeResolver->getArrayType($expr, static fn (Expr $inner): Type => $itemResults[spl_object_id($inner)]->getType()); - foreach ($itemTypes as $itemType) { - $scope = $scope->addTemplateArgumentConstraints(ClosureSignatureInference::collectAbsorbed($itemType, $arrayType)); - } - return $scope; + return $scope->addTemplateArgumentConstraints(ClosureSignatureInference::collectAbsorbedInto($itemTypes, $arrayType)); } private function getExpectedArrayType(?Type $type): ?Type diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index accc87d17c1..684c20df179 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -227,7 +227,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $rightFalseyScope = $accTruthyScope->applySpecifiedTypes($rightTypes($accTruthyScope, TypeSpecifierContext::createFalsey())); $leftExprNode = $accExpr; - $leftTypes = $accTypes; + $leftTypes = self::memoizeSubjectTypes($accTypes); $leftTruthyScope = $accTruthyScope; $leftFalseyScope = $accFalseyScope; $accTypes = fn (MutatingScope $scope, TypeSpecifierContext $ctx): SpecifiedTypes => $this->booleanNarrowingHelper->specifyConjunction( @@ -264,4 +264,31 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex ); } + /** + * The narrowing of the conjunction folded so far: every later conjunction + * asks it again as its left operand, with the scopes and contexts it was + * already asked with - answered once per pair instead of re-folding every + * subject before it on each ask. + * + * @param Closure(MutatingScope, TypeSpecifierContext): SpecifiedTypes $types + * @return Closure(MutatingScope, TypeSpecifierContext): SpecifiedTypes + */ + private static function memoizeSubjectTypes(Closure $types): Closure + { + $answered = []; + + return static function (MutatingScope $scope, TypeSpecifierContext $context) use ($types, &$answered): SpecifiedTypes { + foreach ($answered as [$answeredScope, $answeredContext, $answer]) { + if ($answeredScope === $scope && $answeredContext === $context) { + return $answer; + } + } + + $answer = $types($scope, $context); + $answered[] = [$scope, $context, $answer]; + + return $answer; + }; + } + } diff --git a/src/Analyser/ExpressionResultStorage.php b/src/Analyser/ExpressionResultStorage.php index 15d4a520915..a3de0d9a208 100644 --- a/src/Analyser/ExpressionResultStorage.php +++ b/src/Analyser/ExpressionResultStorage.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; +use PHPStan\Analyser\Generics\TemplateArgumentFrame; use PHPStan\Turbo\ShadowedByTurboExtension; use SplObjectStorage; @@ -19,6 +20,17 @@ final class ExpressionResultStorage */ private ?self $fallback = null; + /** + * The walks of closure bodies run for invocations of closures whose by-ref + * uses are followed (see ClosureProcessor::processByRefInvocation()), + * keyed by the closure: [frame, until fixpoint, native types promoted, + * entry scope, exit scope, throw points]. The walk is a function of these, + * so an invocation entering the body in a state already walked reads it. + * + * @var SplObjectStorage}>>|null + */ + private ?SplObjectStorage $byRefInvocationWalks = null; + public function __construct() { $this->exprResults = new SplObjectStorage(); @@ -46,4 +58,30 @@ public function findExpressionResult(Expr $expr): ?ExpressionResult return $this->exprResults[$expr] ?? ($this->fallback !== null ? $this->fallback->findExpressionResult($expr) : null); } + /** + * @param array{TemplateArgumentFrame|null, bool, bool, MutatingScope, MutatingScope, list} $walk + */ + public function storeByRefInvocationWalk(Expr\Closure $closure, array $walk): void + { + $this->byRefInvocationWalks ??= new SplObjectStorage(); + $walks = $this->byRefInvocationWalks[$closure] ?? []; + $walks[] = $walk; + $this->byRefInvocationWalks[$closure] = $walks; + } + + /** + * The walks stored here and in the storages this one falls back to. + * + * @return list}> + */ + public function findByRefInvocationWalks(Expr\Closure $closure): array + { + $walks = $this->byRefInvocationWalks[$closure] ?? []; + if ($this->fallback === null) { + return $walks; + } + + return [...$walks, ...$this->fallback->findByRefInvocationWalks($closure)]; + } + } diff --git a/src/Analyser/Generics/ClosureSignatureInference.php b/src/Analyser/Generics/ClosureSignatureInference.php index d715734b5e2..c2062519c1c 100644 --- a/src/Analyser/Generics/ClosureSignatureInference.php +++ b/src/Analyser/Generics/ClosureSignatureInference.php @@ -289,21 +289,38 @@ public static function collectInvokedCallee(Type $calleeType): TemplateArgumentC * they escape. */ public static function collectAbsorbed(Type $input, Type $result): TemplateArgumentConstraints + { + return self::collectAbsorbedInto([$input], $result); + } + + /** + * The values of $inputs merged into $result - the items of an array + * literal, the members of a union. See collectAbsorbed(). The markers + * $result kept are collected once for all of them: a large literal of + * closure-carrying items would otherwise traverse the whole array type + * once per item. + * + * @param list $inputs + */ + public static function collectAbsorbedInto(array $inputs, Type $result): TemplateArgumentConstraints { $constraints = TemplateArgumentConstraints::createEmpty(); - if ($input === $result) { - return $constraints; - } - $markers = self::collectMarkers($input); - if ($markers === []) { - return $constraints; - } - $kept = self::collectMarkers($result); - foreach ($markers as $key => $marker) { - if (isset($kept[$key])) { + $kept = null; + foreach ($inputs as $input) { + if ($input === $result) { continue; } - $constraints = $constraints->withUnconstrainingSend($marker); + $markers = self::collectMarkers($input); + if ($markers === []) { + continue; + } + $kept ??= self::collectMarkers($result); + foreach ($markers as $key => $marker) { + if (isset($kept[$key])) { + continue; + } + $constraints = $constraints->withUnconstrainingSend($marker); + } } return $constraints; @@ -330,12 +347,7 @@ public static function collectAbsorbedInUnion(array $types): TemplateArgumentCon return $constraints; } - $union = TypeCombinator::union(...$types); - foreach ($types as $type) { - $constraints = $constraints->merge(self::collectAbsorbed($type, $union)); - } - - return $constraints; + return self::collectAbsorbedInto($types, TypeCombinator::union(...$types)); } /** Whether $type carries a closure written where nothing types it. */ diff --git a/src/Analyser/Generics/StaticVariableInference.php b/src/Analyser/Generics/StaticVariableInference.php index e6b38e8e892..d7cb9ac1d57 100644 --- a/src/Analyser/Generics/StaticVariableInference.php +++ b/src/Analyser/Generics/StaticVariableInference.php @@ -46,6 +46,8 @@ final class StaticVariableInference private const SITES_ATTRIBUTE = 'staticVariableInferenceSites'; + private const LAST_FUNCTION_LIKE_ATTRIBUTE = 'staticVariableInferenceLastFunctionLike'; + public function __construct( private ReflectionProvider $reflectionProvider, #[AutowiredParameter(ref: '%featureToggles.staticVariablesFromUsages%')] @@ -79,6 +81,25 @@ public function getSites(Node $functionLike, array $stmts): array return $sites; } + /** + * Whether a statement from $index on holds a closure, an arrow function or + * a class: the walks inferring the `static` variables walk their bodies + * without resolving template arguments, unlike the second pass. + * + * @param Node\Stmt[] $stmts + */ + public function hasFunctionLikeFrom(Node $functionLike, array $stmts, int $index): bool + { + /** @var int|null $last */ + $last = $functionLike->getAttribute(self::LAST_FUNCTION_LIKE_ATTRIBUTE); + if ($last === null) { + $last = self::findLastFunctionLikeStatement($stmts); + $functionLike->setAttribute(self::LAST_FUNCTION_LIKE_ATTRIBUTE, $last); + } + + return $last >= $index; + } + /** Whether the current walk infers the type of the `static` variable. */ public function isInferred(MutatingScope $scope, Expr\Variable $var): bool { @@ -297,6 +318,42 @@ private static function scanSites(array $stmts): array return $sites; } + /** + * The index of the last statement holding a function-like or a class, -1 + * when none does. + * + * @param Node\Stmt[] $stmts + */ + private static function findLastFunctionLikeStatement(array $stmts): int + { + $last = -1; + foreach ($stmts as $index => $stmt) { + $stack = [$stmt]; + while (count($stack) > 0) { + $node = array_pop($stack); + if ($node instanceof Node\FunctionLike || $node instanceof Node\Stmt\ClassLike) { + $last = $index; + break; + } + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->$subNodeName; + if ($subNode instanceof Node) { + $stack[] = $subNode; + } elseif (is_array($subNode)) { + foreach ($subNode as $item) { + if (!$item instanceof Node) { + continue; + } + $stack[] = $item; + } + } + } + } + } + + return $last; + } + /** * @param array $names */ diff --git a/src/Analyser/Generics/TemplateArgumentObserver.php b/src/Analyser/Generics/TemplateArgumentObserver.php index c96bc05d3d2..c3eaf43a5d4 100644 --- a/src/Analyser/Generics/TemplateArgumentObserver.php +++ b/src/Analyser/Generics/TemplateArgumentObserver.php @@ -192,8 +192,14 @@ private function observeClosureSend(TemplateArgumentConstraints $constraints, Ty && $declared->isIterable()->yes() ) { $constraints = $this->observeClosureSend($constraints, $declared->getIterableKeyType(), $actual->getIterableKeyType()); + $declaredValueType = $declared->getIterableValueType(); + if ($declaredValueType instanceof MixedType && !$declaredValueType instanceof TemplateType) { + // every value sent to plain mixed escapes - collected from the + // array itself, not from the union of a large constant array's values + return $this->escapeClosures($constraints, $actual); + } - return $this->observeClosureSend($constraints, $declared->getIterableValueType(), $actual->getIterableValueType()); + return $this->observeClosureSend($constraints, $declaredValueType, $actual->getIterableValueType()); } return $this->escapeClosures($constraints, $actual); @@ -501,12 +507,30 @@ private function observeSend(TemplateArgumentConstraints $constraints, Type $dec return $constraints; } - $constraints = $this->observeSend($constraints, $declared->getIterableKeyType(), $actual->getIterableKeyType(), $isCallArgument); - $constraints = $this->observeSend($constraints, $declared->getIterableValueType(), $actual->getIterableValueType(), $isCallArgument); + // the key and value types of a large constant array are the unions of + // all its keys and values - not built for a target they cannot constrain + $declaredKeyType = $declared->getIterableKeyType(); + if (!self::isInertSendTarget($declaredKeyType, $isCallArgument)) { + $constraints = $this->observeSend($constraints, $declaredKeyType, $actual->getIterableKeyType(), $isCallArgument); + } + $declaredValueType = $declared->getIterableValueType(); + if (!self::isInertSendTarget($declaredValueType, $isCallArgument)) { + $constraints = $this->observeSend($constraints, $declaredValueType, $actual->getIterableValueType(), $isCallArgument); + } return $constraints; } + /** + * observeSend() observes nothing sent to plain mixed outside a call + * argument: a union recurses into its members, a bare marker never + * constrains, mixed names no class and is not certainly iterable. + */ + private static function isInertSendTarget(Type $declared, bool $isCallArgument): bool + { + return !$isCallArgument && $declared instanceof MixedType && !$declared instanceof TemplateType; + } + /** * An argument was passed to a parameter: the argument's markers are sent to * the parameter type, and a parameter type carrying the receiver's markers diff --git a/src/Analyser/Generics/TemplateArgumentStats.php b/src/Analyser/Generics/TemplateArgumentStats.php index 1ee422d6c02..89b20e386fd 100644 --- a/src/Analyser/Generics/TemplateArgumentStats.php +++ b/src/Analyser/Generics/TemplateArgumentStats.php @@ -58,6 +58,7 @@ final class TemplateArgumentStats 'closureObservationPasses' => 0, 'closureObservationStatements' => 0, 'closureObservationStatementsReplayed' => 0, + 'staticVariableWalksReplayed' => 0, ]; public static function enableFromEnvironment(): void diff --git a/src/Analyser/StatementsHandler.php b/src/Analyser/StatementsHandler.php index 87e159c04b7..42ddc16f191 100644 --- a/src/Analyser/StatementsHandler.php +++ b/src/Analyser/StatementsHandler.php @@ -64,6 +64,15 @@ final class StatementsHandler { + /** The last walk inferring the `static` variables walked a statement: [kind, recording from, recording to] */ + private const STATIC_WALK_WALKED = 0; + + /** It carried a statement's recorded effect over: [kind, statement index, 0] */ + private const STATIC_WALK_CARRIED = 1; + + /** It converged with the observation pass at a statement - the rest stands: [kind, statement index, 0] */ + private const STATIC_WALK_REST = 2; + private const MENTIONED_VARIABLES_ATTRIBUTE = 'templateArgumentMentionedVariables'; /** PHPSTAN_TEMPLATE_ARGUMENTS_DEBUG=1 prints every second-pass re-walk/replay decision. */ @@ -584,16 +593,24 @@ private function processBodyStmtNodesTwoPass( $staticVariableTypes = []; $staticVariableConditionalExpressions = []; $staticStatementIndexes = []; + $staticWalk = null; if ($staticSites !== []) { foreach ($staticSites as [, $index]) { $staticStatementIndexes[$index] = true; } - [$staticVariableTypes, $staticVariableConditionalExpressions, $constraints] = $this->inferStaticVariableTypes($nodeScopeResolver, $parentNode, $stmts, $staticSites, $staticStatementIndexes, $parentFrame, $entries, $recording, $storage, $observationContext); + [$staticVariableTypes, $staticVariableConditionalExpressions, $constraints, $staticWalk] = $this->inferStaticVariableTypes($nodeScopeResolver, $parentNode, $stmts, $staticSites, $staticStatementIndexes, $parentFrame, $entries, $recording, $storage, $observationContext); } $frame = $this->templateArgumentResolver->resolve($constraints, $parentFrame, $statementStartTokenPositions, $parentNode, $stmts); if ($staticVariableTypes !== []) { $frame = $frame->withStaticVariableTypes($staticVariableTypes, $staticStatementIndexes, $staticVariableConditionalExpressions); } + if ( + $staticWalk !== null + && $constraints->isEmpty() + && !$this->staticVariableInference->hasFunctionLikeFrom($parentNode, $stmts, $staticWalk[0]) + ) { + return $this->replayStaticVariableWalk($nodeScopeResolver, $stmts, $entries, $recording, $staticWalk, $frame, $parentFrame, $parentConstraints, $storage, $nodeCallback, $scope); + } if ($frame->isObservingClosures()) { $frame = $this->observeClosureSignatures($nodeScopeResolver, $parentNode, $stmts, $frame, $entries, $storage, $observationContext, $statementStartTokenPositions); } @@ -685,7 +702,8 @@ private function processBodyStmtNodesTwoPass( * statements that read a variable whose type changed - until the type at * each `static` statement holds every type the variable takes after it, * generalized like a loop's variables. The facts of the last walk are the - * ones the template arguments and closure signatures are resolved from. + * ones the template arguments and closure signatures are resolved from; the + * walk itself is returned too, see replayStaticVariableWalk(). * * The variables of a run of `static` statements (see * StaticVariableInference::getRuns()) hold one of the states a call left @@ -697,7 +715,7 @@ private function processBodyStmtNodesTwoPass( * @param non-empty-list $staticSites * @param array $staticStatementIndexes * @param array $entries - * @return array{array, array}>, TemplateArgumentConstraints} + * @return array{array, array}>, TemplateArgumentConstraints, array{int, StatementListWalkState, RecordingNodeCallback, ExpressionResultStorage, list}} */ private function inferStaticVariableTypes( NodeScopeResolver $nodeScopeResolver, @@ -741,6 +759,8 @@ private function inferStaticVariableTypes( $state->scope = $state->scope->withTemplateArgumentFrame($frame); $walkStorage = $storage->duplicate(); $walkRecording = new RecordingNodeCallback(); + /** @var list $walkLog see replayStaticVariableWalk() */ + $walkLog = []; /** @var list $replayedScopes */ $replayedScopes = []; /** @var list $carriedOverScopes the entry scopes of the statements not walked again */ @@ -758,6 +778,7 @@ private function inferStaticVariableTypes( // converged with the observation pass: the rest of it stands $state->scope = $this->withRecordedConstraints($state->scope, $recordedEntry->scope, $entries[$stmtCount][0]->scope); $this->appendRecordedStatementResults($state, $recordedEntry, $entries[$stmtCount][0]); + $walkLog[] = [self::STATIC_WALK_REST, $i, 0]; break; } @@ -768,13 +789,16 @@ private function inferStaticVariableTypes( || $frame->ownsSiteInStatement($i) || $this->statementMentionsAnyVariable($stmt, $differingRoots) ) { + $from = $walkRecording->count(); $this->processStatementStep($nodeScopeResolver, $parentNode, $stmts, $i, $stmt, $state, $walkStorage, $walkRecording, $context, true); + $walkLog[] = [self::STATIC_WALK_WALKED, $from, $walkRecording->count()]; $replayedScopes[] = $state->scope; continue; } // the statement does not read what changed: the variables keep // their types through it + $walkLog[] = [self::STATIC_WALK_CARRIED, $i, 0]; $replayedScopes[] = $state->scope; $carriedOverScopes[] = $state->scope; $recordedExit = $entries[$i + 1][0]; @@ -847,7 +871,65 @@ private function inferStaticVariableTypes( $siteTypes[spl_object_id($var)] = [$var, $type, $nativeType]; } - return [$siteTypes, $conditionalExpressions, $constraints]; + // every way out of the loop leaves the last walk made with the types + // and conditional expressions returned + return [$siteTypes, $conditionalExpressions, $constraints, [$start, $state, $walkRecording, $walkStorage, $walkLog]]; + } + + /** + * The second pass of a body whose only sites are its `static` statements + * and which observed no template argument or closure signature: it walks + * the statements from the first `static` one with the resolved types, as + * the last walk inferring them did - with no marker to resolve, a walk + * that observes computes what one that does not observe computes. That + * walk's recording and results stand for it. A closure or class in these + * statements is walked differently by the second pass (resolving its own + * template arguments), so the second pass walks those bodies itself. + * + * @param Node\Stmt[] $stmts + * @param array $entries + * @param array{int, StatementListWalkState, RecordingNodeCallback, ExpressionResultStorage, list} $staticWalk + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + private function replayStaticVariableWalk( + NodeScopeResolver $nodeScopeResolver, + array $stmts, + array $entries, + RecordingNodeCallback $recording, + array $staticWalk, + TemplateArgumentFrame $frame, + ?TemplateArgumentFrame $parentFrame, + ?TemplateArgumentConstraints $parentConstraints, + ExpressionResultStorage $storage, + callable $nodeCallback, + MutatingScope $scope, + ): InternalStatementResult + { + [$start, $state, $walkRecording, $walkStorage, $walkLog] = $staticWalk; + $stmtCount = count($stmts); + if (TemplateArgumentStats::$enabled) { + TemplateArgumentStats::increment('bodiesWithSites'); + TemplateArgumentStats::increment('staticVariableWalksReplayed'); + } + $nodeScopeResolver->replayRecordingRange($recording, 0, $entries[$start][1], $nodeCallback, $storage, $scope); + $storage->mergeResults($walkStorage); + foreach ($walkLog as [$kind, $a, $b]) { + if ($kind === self::STATIC_WALK_WALKED) { + $nodeScopeResolver->replayRecordingRange($walkRecording, $a, $b, $nodeCallback, $storage, $scope); + continue; + } + if ($kind === self::STATIC_WALK_CARRIED) { + $nodeScopeResolver->replayRecordingRange($recording, $entries[$a][1], $entries[$a + 1][1], $nodeCallback, $storage, $scope); + continue; + } + $nodeScopeResolver->replayRecordingRange($recording, $entries[$a][1], $recording->count(), $nodeCallback, $storage, $scope); + // the recorded end scope, as the second pass takes it + $state->scope = $entries[$stmtCount][0]->scope; + } + $this->processDeferredByRefClosureBodies($nodeScopeResolver, $frame, $state, $storage, $nodeCallback); + + $state->scope = $state->scope->withTemplateArgumentFrame($parentFrame)->withTemplateArgumentConstraints($parentConstraints); + return $state->toResult(); } /** diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 72f6aef66ca..e77f9fdcba9 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = 'f0ebc74'; + public const EXPECTED_EXTENSION_VERSION = '6888887'; private static bool $active = false; diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 6508e03ce58..6d4ba2dd624 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -1385,6 +1385,69 @@ public static function countConstantArrayValueTypes(array $types): int return $constantArrayValuesCount; } + /** + * The value at each key of the array that is a single constant scalar, null + * when the array has real unsealed extras - its keys-supersets are then not + * decided by the known keys alone. + * + * @return array|null + */ + private static function getConstantScalarValuesByKey(ConstantArrayType $array): ?array + { + $unsealed = $array->getUnsealedTypes(); + if ($unsealed !== null && !($unsealed[0] instanceof NeverType && $unsealed[0]->isExplicit())) { + return null; + } + + $values = []; + $valueTypes = $array->getValueTypes(); + foreach ($array->getKeyTypes() as $i => $keyType) { + if (!$valueTypes[$i]->isConstantScalarValue()->yes()) { + continue; + } + $constantScalarValues = $valueTypes[$i]->getConstantScalarValues(); + if (count($constantScalarValues) !== 1) { + continue; + } + $values[$keyType->getValue()] = $constantScalarValues[0]; + } + + return $values; + } + + /** + * Whether two arrays differ in enough keys holding different single constant + * scalars that neither can be a keys-superset of the other: each such key + * pairs two disjoint values, and ConstantArrayType::isKeysSupersetOf() lets + * at most one key's values be unrelated - only when both arrays have the + * same number of keys, two or more. + * + * @param array|null $a + * @param array|null $b + */ + private static function haveDisjointConstantScalarValues(?array $a, ?array $b, bool $oneUnrelatedKeyAllowed): bool + { + if ($a === null || $b === null) { + return false; + } + if (count($a) > count($b)) { + [$a, $b] = [$b, $a]; + } + + $differing = 0; + foreach ($a as $key => $value) { + if (!array_key_exists($key, $b) || $b[$key] === $value) { + continue; + } + $differing++; + if ($differing >= 2 || !$oneUnrelatedKeyAllowed) { + return true; + } + } + + return false; + } + /** * @param list $constantArrays * @return list @@ -1431,6 +1494,15 @@ private static function reduceArrays(array $constantArrays, bool $preserveTagged } } + // the single constant scalar value at each key of the arrays without + // real unsealed extras (whose supersets the key/value shape decides) + $constantScalarValues = []; + if ($preserveTaggedUnions) { + foreach ($arraysToProcess as $i => $arrayToProcess) { + $constantScalarValues[$i] = self::getConstantScalarValuesByKey($arrayToProcess); + } + } + $eligibleCombinations = []; foreach ($arraysToProcessPerKey as $arrays) { @@ -1468,17 +1540,32 @@ private static function reduceArrays(array $constantArrays, bool $preserveTagged $unionValueType = self::union($iValueType, $jValueType); if (!$unionValueType instanceof UnionType) { $arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]); + $constantScalarValues[$j] = null; unset($arraysToProcess[$i]); continue 2; } } + if ( + $preserveTaggedUnions + && self::haveDisjointConstantScalarValues( + $constantScalarValues[$i] ?? null, + $constantScalarValues[$j] ?? null, + count($arraysToProcess[$i]->getKeyTypes()) === count($arraysToProcess[$j]->getKeyTypes()) + && count($arraysToProcess[$i]->getKeyTypes()) >= 2, + ) + ) { + // neither array is a keys-superset of the other + continue; + } + if ( $preserveTaggedUnions && $overlappingKeysCount === count($arraysToProcess[$i]->getKeyTypes()) && $arraysToProcess[$j]->isKeysSupersetOf($arraysToProcess[$i]) ) { $arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]); + $constantScalarValues[$j] = null; unset($arraysToProcess[$i]); continue 2; } @@ -1489,6 +1576,7 @@ private static function reduceArrays(array $constantArrays, bool $preserveTagged && $arraysToProcess[$i]->isKeysSupersetOf($arraysToProcess[$j]) ) { $arraysToProcess[$i] = $arraysToProcess[$i]->mergeWith($arraysToProcess[$j]); + $constantScalarValues[$i] = null; unset($arraysToProcess[$j]); continue 1; } @@ -1656,6 +1744,14 @@ private static function reduceArrays(array $constantArrays, bool $preserveTagged * not such a value, in which case the caller falls back to the general A & (B|C) * distribution. */ + /** @phpstan-assert-if-true UnionType $type */ + private static function isPlainOrBenevolentUnion(Type $type): bool + { + $class = get_class($type); + + return $class === UnionType::class || $class === BenevolentUnionType::class; + } + private static function intersectFiniteUnions(UnionType $a, UnionType $b): ?Type { $membersA = self::finiteUnionMembers($a); @@ -1739,19 +1835,24 @@ public static function doIntersect(Type ...$types): Type $unionTypesCount++; } - // Fast path: the intersection of two plain unions whose members are all finite, + // Fast path: the intersection of two unions whose members are all finite, // mutually-disjoint values (constant scalars and/or enum cases) is their // identity-keyed set intersection (O(n)), avoiding the O(n*m) `A & (B|C)` - // distribution + union rebuild below. Restricted to the exact UnionType class so - // BenevolentUnionType and the template union types keep their dedicated handling. + // distribution + union rebuild below. A benevolent union is the one the + // distribution walks first and the benevolence it gives the result is kept; + // the template union types keep their dedicated handling. if ( $typesCount === 2 - && get_class($types[0]) === UnionType::class - && get_class($types[1]) === UnionType::class + && self::isPlainOrBenevolentUnion($types[0]) + && self::isPlainOrBenevolentUnion($types[1]) ) { - $finiteIntersection = self::intersectFiniteUnions($types[0], $types[1]); + [$a, $b] = $types; + if ($b instanceof BenevolentUnionType && !$a instanceof BenevolentUnionType) { + [$a, $b] = [$b, $a]; + } + $finiteIntersection = self::intersectFiniteUnions($a, $b); if ($finiteIntersection !== null) { - return $finiteIntersection; + return $a instanceof BenevolentUnionType ? TypeUtils::toBenevolentUnion($finiteIntersection) : $finiteIntersection; } } diff --git a/tests/bench/BenchCase.php b/tests/bench/BenchCase.php index 755b3f3532d..779718d72a8 100644 --- a/tests/bench/BenchCase.php +++ b/tests/bench/BenchCase.php @@ -2,20 +2,10 @@ namespace PHPStan\Benchmark; -use LogicException; use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Error; use PHPStan\Testing\PHPStanTestCaseTrait; -use function fclose; -use function fgets; -use function fopen; -use function preg_match; -use function sprintf; -use function str_contains; -use function str_starts_with; -use function version_compare; -use const PHP_VERSION; abstract class BenchCase { @@ -29,44 +19,6 @@ public static function getAdditionalConfigFiles(): array ]; } - /** - * Data files that cannot be parsed on the current PHP version are not worth - * benchmarking - the analysis stops at the syntax error. - * - * Copy of TypeInferenceTestCase::isFileLintSkipped(), originally from - * https://github.com/php-parallel-lint/PHP-Parallel-Lint/blob/0c2706086ac36dce31967cb36062ff8915fe03f7/bin/skip-linting.php - * - * Copyright (c) 2012, Jakub Onderka - */ - protected static function isFileLintSkipped(string $file): bool - { - $f = @fopen($file, 'r'); - if ($f !== false) { - $firstLine = fgets($f); - if ($firstLine === false) { - return false; - } - - // ignore shebang line - if (str_starts_with($firstLine, '#!')) { - $firstLine = fgets($f); - if ($firstLine === false) { - return false; - } - } - - @fclose($f); - - if (preg_match('~ diff --git a/tests/bench/RegressionBench.php b/tests/bench/RegressionBench.php index a8929c0bcbe..bef98a0789f 100644 --- a/tests/bench/RegressionBench.php +++ b/tests/bench/RegressionBench.php @@ -2,31 +2,26 @@ namespace PHPStan\Benchmark; +use PhpBench\Attributes as Bench; use Symfony\Component\Finder\Finder; -/** - * PHPBench annotations are used instead of attributes so that the benchmark - * also runs on PHP 7.4 with the downgraded source code, where attributes - * are just comments. - * - * The assertion expression has to stay on a single line - an annotation value - * cannot span multiple lines. Annotation names must not be mentioned anywhere - * else in this docblock either, the annotation reader tries to parse them. - * - * @Revs(1) - * @Iterations(5) - * @Warmup(1) - * @RetryThreshold(10.0) - * @Assert("(mode(baseline.time.avg) < 100 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 50%) or (mode(baseline.time.avg) >= 100 milliseconds and mode(baseline.time.avg) < 500 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 25%) or (mode(baseline.time.avg) >= 500 milliseconds and mode(baseline.time.avg) < 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 20%) or (mode(baseline.time.avg) >= 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 10%)") - */ +#[Bench\Revs(revs: 1)] +#[Bench\Iterations(iterations: 5)] +#[Bench\Warmup(revs: 1)] +#[Bench\RetryThreshold(retryThreshold: 10.0)] +#[Bench\Assert(expression: ' + (mode(baseline.time.avg) < 100 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 50%) + or (mode(baseline.time.avg) >= 100 milliseconds and mode(baseline.time.avg) < 500 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 25%) + or (mode(baseline.time.avg) >= 500 milliseconds and mode(baseline.time.avg) < 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 20%) + or (mode(baseline.time.avg) >= 2000 milliseconds and mode(variant.time.avg) < mode(baseline.time.avg) +/- 10%) +')] class RegressionBench extends BenchCase { /** - * @ParamProviders({"provideFiles"}) - * * @param array{string} $params */ + #[Bench\ParamProviders(['provideFiles'])] public function benchRunAnalyse(array $params): void { $this->runAnalyse($params[0]); @@ -47,10 +42,6 @@ private static function findTestDataFilesFromDirectory(string $directory): array $finder->sortByName(true); $files = []; foreach ($finder->files()->name('*.php')->in($directory) as $fileInfo) { - if (self::isFileLintSkipped($fileInfo->getPathname())) { - continue; - } - $files[$fileInfo->getBasename()] = [$fileInfo->getPathname()]; } diff --git a/tests/bench/storage/baseline-7.4.xml b/tests/bench/storage/baseline-7.4.xml deleted file mode 100644 index 4c733cfe030..00000000000 --- a/tests/bench/storage/baseline-7.4.xml +++ /dev/null @@ -1,690 +0,0 @@ - - - - - - Linux - runnervmvrwv9 - 6.17.0-1020-azure - #20~24.04.1-Ubuntu SMP Fri Jun 19 20:09:14 UTC 2026 - x86_64 - - - - 7.4.33 - /etc/php/7.4/cli/php.ini - Core, date, libxml, openssl, pcre, zlib, filter, hash, pcntl, Reflection, SPL, session, standard, sodium, mysqlnd, PDO, xml, amqp, apcu, ast, bcmath, bz2, calendar, ctype, curl, dba, dom, enchant, mbstring, FFI, fileinfo, ftp, gd, gettext, gmp, iconv, igbinary, imagick, imap, intl, json, ldap, exif, memcache, mongodb, msgpack, mysqli, odbc, pdo_dblib, PDO_Firebird, pdo_mysql, PDO_ODBC, pdo_pgsql, pdo_sqlite, pdo_sqlsrv, pgsql, Phar, posix, pspell, readline, shmop, SimpleXML, snmp, soap, sockets, sqlite3, sqlsrv, sysvmsg, sysvsem, sysvshm, tidy, tokenizer, xmlreader, xmlrpc, xmlwriter, xsl, yaml, zip, zmq, memcached, redis, ds, Zend OPcache - - - 1 - - - - 1.572265625 - 0.69873046875 - 0.2646484375 - - - git - 2.2.x - 81e06a5839991d8d3f3b17c97f6e92eb0a6f5906 - - - 0.0090599060058594 - 0.1530647277832 - 1.8279552459717 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/bench/storage/baseline-8.5.xml b/tests/bench/storage/baseline.xml similarity index 100% rename from tests/bench/storage/baseline-8.5.xml rename to tests/bench/storage/baseline.xml diff --git a/turbo-ext/src/ArrayHandler.cpp b/turbo-ext/src/ArrayHandler.cpp index 4e999d6d1cc..ef35055b328 100644 --- a/turbo-ext/src/ArrayHandler.cpp +++ b/turbo-ext/src/ArrayHandler.cpp @@ -412,13 +412,9 @@ class ArrayHandler if (UNEXPECTED(arrayType.isUndef())) return zv::Val(); } zv::Val itemTypesHold(std::move(itemTypes)); - for (zv::ArrayEntry entry : zv::ArrRef(itemTypesHold.raw())) { - zv::Val absorbed = pt_closure_signature_inference_collect_absorbed(entry.value().deref().raw(), arrayType.raw()); - if (UNEXPECTED(absorbed.isUndef())) return zv::Val(); - scope = pt_mutating_scope_add_template_argument_constraints(Z_OBJ_P(scope.raw()), absorbed.raw()); - if (UNEXPECTED(scope.isUndef())) return zv::Val(); - } - return scope; + zv::Val absorbed = pt_closure_signature_inference_collect_absorbed_into(itemTypesHold.raw(), arrayType.raw()); + if (UNEXPECTED(absorbed.isUndef())) return zv::Val(); + return pt_mutating_scope_add_template_argument_constraints(Z_OBJ_P(scope.raw()), absorbed.raw()); } /* the handler entry (Engine.h) */ diff --git a/turbo-ext/src/ClosureProcessor.cpp b/turbo-ext/src/ClosureProcessor.cpp index 9f56650eedc..20a0b041491 100644 --- a/turbo-ext/src/ClosureProcessor.cpp +++ b/turbo-ext/src/ClosureProcessor.cpp @@ -343,6 +343,89 @@ class ClosureProcessor zv::Val stmtsHold = zv::Val::copyOf(zv::Ref(stmts)); zv::Val entryScope = enter(scope); if (UNEXPECTED(entryScope.isUndef())) return zv::Val(); + zv::Val frame = pt_mutating_scope_get_current_template_argument_frame(Z_OBJ_P(scope)); + if (UNEXPECTED(frame.isUndef())) return zv::Val(); + bool nativeTypesPromoted; + if (UNEXPECTED(!pt_mutating_scope_native_types_promoted(Z_OBJ_P(scope), nativeTypesPromoted))) return zv::Val(); + // the body entered in a state it was already walked from, in the same + // pass: a closure invoked many times mostly re-enters one of a few states + zv::Val walk; + { + zv::Val storedWalks = pt_expression_result_storage_find_by_ref_invocation_walks(storage, expr); + if (UNEXPECTED(storedWalks.isUndef())) return zv::Val(); + for (zv::ArrayEntry entry : zv::ArrRef(storedWalks.raw())) { + zval *storedWalk = entry.value().deref().raw(); + zval *walkFrame = zend_hash_index_find(Z_ARRVAL_P(storedWalk), 0); + zval *walkUntilFixpoint = zend_hash_index_find(Z_ARRVAL_P(storedWalk), 1); + zval *walkNativeTypesPromoted = zend_hash_index_find(Z_ARRVAL_P(storedWalk), 2); + zval *walkEntryScope = zend_hash_index_find(Z_ARRVAL_P(storedWalk), 3); + if (UNEXPECTED(walkFrame == NULL || walkUntilFixpoint == NULL || walkNativeTypesPromoted == NULL || walkEntryScope == NULL)) continue; + bool sameFrame = Z_TYPE_P(walkFrame) == IS_OBJECT + ? Z_TYPE_P(frame.raw()) == IS_OBJECT && Z_OBJ_P(walkFrame) == Z_OBJ_P(frame.raw()) + : Z_TYPE_P(frame.raw()) != IS_OBJECT; + if (!sameFrame || (Z_TYPE_P(walkUntilFixpoint) == IS_TRUE) != untilFixpoint || (Z_TYPE_P(walkNativeTypesPromoted) == IS_TRUE) != nativeTypesPromoted) continue; + bool equals = false; + if (UNEXPECTED(!pt_mutating_scope_equals(Z_OBJ_P(walkEntryScope), Z_OBJ_P(entryScope.raw()), equals))) return zv::Val(); + if (!equals) continue; + walk = zv::Val::copyOf(zv::Ref(storedWalk)); + break; + } + } + if (walk.isUndef()) { + zv::Val walked = walkByRefInvocation(nodeScopeResolver, expr, scope, storage, entryScope.raw(), enter, byRefUses.raw(), stmtsHold.raw(), untilFixpoint); + if (UNEXPECTED(walked.isUndef())) return zv::Val(); + zv::Arr stored = zv::Arr::create(6); + stored.push(zv::Val::copyOf(zv::Ref(frame.raw()))); + stored.push(zv::Val::boolean(untilFixpoint)); + stored.push(zv::Val::boolean(nativeTypesPromoted)); + stored.push(zv::Val::copyOf(zv::Ref(entryScope.raw()))); + stored.push(zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(walked.raw()), 0)))); + stored.push(zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(walked.raw()), 1)))); + walk = zv::Val(std::move(stored)); + if (UNEXPECTED(!pt_expression_result_storage_store_by_ref_invocation_walk(storage, expr, walk.raw()))) return zv::Val(); + } + zval *exitScope = zend_hash_index_find(Z_ARRVAL_P(walk.raw()), 4); + zval *bodyThrowPoints = zend_hash_index_find(Z_ARRVAL_P(walk.raw()), 5); + if (UNEXPECTED(exitScope == NULL || bodyThrowPoints == NULL)) return zv::Val(); + + zv::Arr throwPoints = zv::Arr::empty(); + for (zv::ArrayEntry entry : zv::ArrRef(bodyThrowPoints)) { + zval *throwPoint = entry.value().deref().raw(); + zv::Val throwPointScopeHold; + zval *throwPointScope = pt_internal_throw_point_scope(throwPoint, throwPointScopeHold); + if (UNEXPECTED(throwPointScope == NULL)) return zv::Val(); + zv::Val throwScope = assignByRefUses(scope, throwPointScope, byRefUses.raw()); + if (UNEXPECTED(throwScope.isUndef())) return zv::Val(); + bool isExplicit; + if (UNEXPECTED(!pt_internal_throw_point_is_explicit(throwPoint, isExplicit))) return zv::Val(); + zv::Val created; + if (isExplicit) { + zv::Val typeHold; + zval *type = pt_internal_throw_point_type(throwPoint, typeHold); + if (UNEXPECTED(type == NULL)) return zv::Val(); + bool canContainAnyThrowable; + if (UNEXPECTED(!pt_internal_throw_point_can_contain_any_throwable(throwPoint, canContainAnyThrowable))) return zv::Val(); + created = pt_internal_throw_point_create_explicit(throwScope.raw(), type, call, canContainAnyThrowable); + } else { + created = pt_internal_throw_point_create_implicit(throwScope.raw(), call); + } + if (UNEXPECTED(created.isUndef())) return zv::Val(); + throwPoints.push(std::move(created)); + } + + zv::Val resultScope = assignByRefUses(scope, exitScope, byRefUses.raw()); + if (UNEXPECTED(resultScope.isUndef())) return zv::Val(); + zv::Arr pair = zv::Arr::create(2); + pair.push(std::move(resultScope)); + pair.push(std::move(throwPoints)); + return zv::Val(std::move(pair)); + } + + /* Mirrors walkByRefInvocation(): [the body's exit scope, its throw points] */ + template + zv::Val walkByRefInvocation(zval *nodeScopeResolver, zval *expr, zval *scope, zval *storage, zval *firstEntryScope, Enter &enter, zval *byRefUses, zval *stmts, bool untilFixpoint) const + { + zv::Val entryScope = zv::Val::copyOf(zv::Ref(firstEntryScope)); zv::Val exitScope; zv::Arr throwPoints = zv::Arr::empty(); zend_long count = 0; @@ -353,7 +436,7 @@ class ClosureProcessor if (UNEXPECTED(noop.isUndef())) return zv::Val(); zv::Val context = pt_statement_context_create_top_level(false); if (UNEXPECTED(context.isUndef())) return zv::Val(); - zv::Val result = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, expr, stmtsHold.raw(), entryScope.raw(), passStorage.raw(), noop.raw(), context.raw()); + zv::Val result = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, expr, stmts, entryScope.raw(), passStorage.raw(), noop.raw(), context.raw()); if (UNEXPECTED(result.isUndef())) return zv::Val(); zv::Val passExitScope; { @@ -386,35 +469,15 @@ class ClosureProcessor if (UNEXPECTED(resultThrowPoints == NULL)) return zv::Val(); zv::Val throwPointsIterated = zv::Val::copyOf(zv::Ref(resultThrowPoints)); for (zv::ArrayEntry entry : zv::ArrRef(throwPointsIterated.raw())) { - zval *throwPoint = entry.value().deref().raw(); - zv::Val throwPointScopeHold; - zval *throwPointScope = pt_internal_throw_point_scope(throwPoint, throwPointScopeHold); - if (UNEXPECTED(throwPointScope == NULL)) return zv::Val(); - zv::Val throwScope = assignByRefUses(scope, throwPointScope, byRefUses.raw()); - if (UNEXPECTED(throwScope.isUndef())) return zv::Val(); - bool isExplicit; - if (UNEXPECTED(!pt_internal_throw_point_is_explicit(throwPoint, isExplicit))) return zv::Val(); - zv::Val created; - if (isExplicit) { - zv::Val typeHold; - zval *type = pt_internal_throw_point_type(throwPoint, typeHold); - if (UNEXPECTED(type == NULL)) return zv::Val(); - bool canContainAnyThrowable; - if (UNEXPECTED(!pt_internal_throw_point_can_contain_any_throwable(throwPoint, canContainAnyThrowable))) return zv::Val(); - created = pt_internal_throw_point_create_explicit(throwScope.raw(), type, call, canContainAnyThrowable); - } else { - created = pt_internal_throw_point_create_implicit(throwScope.raw(), call); - } - if (UNEXPECTED(created.isUndef())) return zv::Val(); - throwPoints.push(std::move(created)); + throwPoints.push(entry.value().deref()); } } if (!untilFixpoint) break; // the next run starts from any state a run may have left - zv::Val entryAssigned = assignByRefUses(scope, entryScope.raw(), byRefUses.raw()); + zv::Val entryAssigned = assignByRefUses(scope, entryScope.raw(), byRefUses); if (UNEXPECTED(entryAssigned.isUndef())) return zv::Val(); - zv::Val exitAssigned = assignByRefUses(scope, passExitScope.raw(), byRefUses.raw()); + zv::Val exitAssigned = assignByRefUses(scope, passExitScope.raw(), byRefUses); if (UNEXPECTED(exitAssigned.isUndef())) return zv::Val(); zv::Val joined = pt_mutating_scope_merge_with(Z_OBJ_P(entryAssigned.raw()), exitAssigned.raw()); if (UNEXPECTED(joined.isUndef())) return zv::Val(); @@ -439,10 +502,8 @@ class ClosureProcessor exitScope = std::move(merged); } - zv::Val resultScope = assignByRefUses(scope, exitScope.raw(), byRefUses.raw()); - if (UNEXPECTED(resultScope.isUndef())) return zv::Val(); zv::Arr pair = zv::Arr::create(2); - pair.push(std::move(resultScope)); + pair.push(std::move(exitScope)); pair.push(std::move(throwPoints)); return zv::Val(std::move(pair)); } diff --git a/turbo-ext/src/ClosureSignatureInference.cpp b/turbo-ext/src/ClosureSignatureInference.cpp index 4dbe668636e..ac4334ac077 100644 --- a/turbo-ext/src/ClosureSignatureInference.cpp +++ b/turbo-ext/src/ClosureSignatureInference.cpp @@ -1167,16 +1167,34 @@ class ClosureSignatureInference /* Mirrors collectAbsorbed() */ static zv::Val collectAbsorbed(zval *input, zval *result) + { + zv::Arr inputs = zv::Arr::create(1); + inputs.push(zv::Val::copyOf(zv::Ref(input))); + zv::Val inputsHold(std::move(inputs)); + return collectAbsorbedInto(inputsHold.raw(), result); + } + + /* Mirrors collectAbsorbedInto(): the markers $result kept are collected + * once, on the first input carrying any */ + static zv::Val collectAbsorbedInto(zval *inputs, zval *result) { zv::Val constraints = pt_template_argument_constraints_create_empty(); if (UNEXPECTED(constraints.isUndef())) return zv::Val(); - if (Z_TYPE_P(input) == IS_OBJECT && Z_TYPE_P(result) == IS_OBJECT && Z_OBJ_P(input) == Z_OBJ_P(result)) return constraints; - zv::Val markers = collectMarkers(input); - if (UNEXPECTED(markers.isUndef())) return zv::Val(); - if (zend_hash_num_elements(Z_ARRVAL_P(markers.raw())) == 0) return constraints; - zv::Val kept = collectMarkers(result); - if (UNEXPECTED(kept.isUndef())) return zv::Val(); - return unconstrainMarkers(std::move(constraints), markers.raw(), kept.raw()); + zv::Val kept; + for (auto entry : zv::ArrRef(inputs)) { + zval *input = entry.value().deref().raw(); + if (Z_TYPE_P(input) == IS_OBJECT && Z_TYPE_P(result) == IS_OBJECT && Z_OBJ_P(input) == Z_OBJ_P(result)) continue; + zv::Val markers = collectMarkers(input); + if (UNEXPECTED(markers.isUndef())) return zv::Val(); + if (zend_hash_num_elements(Z_ARRVAL_P(markers.raw())) == 0) continue; + if (kept.isUndef()) { + kept = collectMarkers(result); + if (UNEXPECTED(kept.isUndef())) return zv::Val(); + } + constraints = unconstrainMarkers(std::move(constraints), markers.raw(), kept.raw()); + if (UNEXPECTED(constraints.isUndef())) return zv::Val(); + } + return constraints; } /* Mirrors hasMarkers() */ @@ -1212,13 +1230,7 @@ class ClosureSignatureInference zv::Val unionType = pt_type_combinator_union(count, argv); efree(argv); if (UNEXPECTED(unionType.isUndef())) return zv::Val(); - for (auto entry : zv::ArrRef(types)) { - zv::Val absorbed = collectAbsorbed(entry.value().deref().raw(), unionType.raw()); - if (UNEXPECTED(absorbed.isUndef())) return zv::Val(); - constraints = pt_template_argument_constraints_merge(constraints.raw(), absorbed.raw()); - if (UNEXPECTED(constraints.isUndef())) return zv::Val(); - } - return constraints; + return collectAbsorbedInto(types, unionType.raw()); } /* Mirrors collectInvocation() */ @@ -1847,6 +1859,11 @@ zv::Val pt_closure_signature_inference_collect_absorbed(zval *input, zval *resul return ClosureSignatureInference::collectAbsorbed(input, result); } +zv::Val pt_closure_signature_inference_collect_absorbed_into(zval *inputs, zval *result) +{ + return ClosureSignatureInference::collectAbsorbedInto(inputs, result); +} + zv::Val pt_closure_signature_inference_collect_absorbed_in_union(zval *types) { return ClosureSignatureInference::collectAbsorbedInUnion(types); @@ -2007,6 +2024,15 @@ PT_MINIT_REGISTRATION(pt_register_closure_signature_inference) PT_RETURN_VAL(ClosureSignatureInference::collectAbsorbed(input, result)); }); + cls.method(sigs::collectAbsorbedInto, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *inputs, *result; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ARRAY(inputs) + Z_PARAM_OBJECT(result) + ZEND_PARSE_PARAMETERS_END(); + PT_RETURN_VAL(ClosureSignatureInference::collectAbsorbedInto(inputs, result)); + }); + cls.method(sigs::collectAbsorbedInUnion, [](INTERNAL_FUNCTION_PARAMETERS) { zval *types; ZEND_PARSE_PARAMETERS_START(1, 1) diff --git a/turbo-ext/src/ExpressionResultStorage.cpp b/turbo-ext/src/ExpressionResultStorage.cpp index 0d0bcd48f2f..69a85909eb8 100644 --- a/turbo-ext/src/ExpressionResultStorage.cpp +++ b/turbo-ext/src/ExpressionResultStorage.cpp @@ -25,6 +25,7 @@ namespace sigs = ptdecl::ExpressionResultStorage::sig; #include "zv.h" #define PT_ERS_PROP_FALLBACK 2 +#define PT_ERS_PROP_BY_REF_INVOCATION_WALKS 3 zend_class_entry *pt_ce_expression_result_storage = nullptr; @@ -82,6 +83,53 @@ class ExpressionResultStorage } } + /* Mirrors storeByRefInvocationWalk(): the walks keyed by the closure's + * handle as [closure, list of walks] - the closure is pinned like the + * stored Exprs */ + void storeByRefInvocationWalk(zval *closure, zval *walk) + { + zend_ulong id = Z_OBJ_HANDLE_P(closure); + zv::ObjRef obj(self); + zv::ArrRef table(obj.propAt(PT_ERS_PROP_BY_REF_INVOCATION_WALKS).raw()); + zv::Ref entry = table.findIndex(id); + zv::Arr walks = zv::Arr::empty(); + if (entry.raw() != NULL) { + zval *stored = zend_hash_index_find(Z_ARRVAL_P(entry.deref().raw()), 1); + if (stored != NULL) walks = zv::Arr::copyOfTable(Z_ARRVAL_P(stored)); + } + walks.push(zv::Val::copyOf(zv::Ref(walk))); + zv::Arr pair = zv::Arr::create(2); + pair.push(zv::Val::copyOf(zv::Ref(closure))); + pair.push(std::move(walks)); + zv::Val pairHold(std::move(pair)); + table.setIndex(id, zv::Ref(pairHold.raw())); + } + + /* Mirrors findByRefInvocationWalks(): this storage's walks, then the + * fallback chain's */ + zv::Val findByRefInvocationWalks(zval *closure) const + { + zend_ulong id = Z_OBJ_HANDLE_P(closure); + zv::Arr walks = zv::Arr::empty(); + zval *cur = self; + for (;;) { + zv::ObjRef obj(cur); + zv::Ref entry = zv::ArrRef(obj.propAt(PT_ERS_PROP_BY_REF_INVOCATION_WALKS).raw()).findIndex(id); + if (entry.raw() != NULL) { + zval *stored = zend_hash_index_find(Z_ARRVAL_P(entry.deref().raw()), 1); + if (stored != NULL) { + for (zv::ArrayEntry walk : zv::ArrRef(stored)) { + walks.push(zv::Val::copyOf(walk.value())); + } + } + } + zval *fallback = obj.propAt(PT_ERS_PROP_FALLBACK).raw(); + if (Z_TYPE_P(fallback) != IS_OBJECT) break; + cur = fallback; + } + return zv::Val(std::move(walks)); + } + private: zval *self; }; @@ -115,6 +163,9 @@ PT_MINIT_REGISTRATION(pt_register_expression_result_storage) cls.privateArrayProperty("exprsById"); cls.privateArrayProperty("resultsById"); cls.property("fallback", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "self"); + /* the twin's ?SplObjectStorage $byRefInvocationWalks: [closure, walks] + * keyed by the closure's handle */ + cls.privateArrayProperty("byRefInvocationWalksById"); /* the twin's constructor only initialized its SplObjectStorage; the * native property defaults already cover that */ @@ -163,6 +214,27 @@ PT_MINIT_REGISTRATION(pt_register_expression_result_storage) ExpressionResultStorage(ZEND_THIS).findExpressionResult(expr).intoReturnValue(return_value); }); + cls.method(sigs::storeByRefInvocationWalk, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *closure, *walk; + zend_class_entry *closureCe = pt_class(PT_CLASS_CLOSURE_EXPR); + if (UNEXPECTED(closureCe == NULL)) RETURN_THROWS(); + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(closure, closureCe) + Z_PARAM_ARRAY(walk) + ZEND_PARSE_PARAMETERS_END(); + ExpressionResultStorage(ZEND_THIS).storeByRefInvocationWalk(closure, walk); + }); + + cls.method(sigs::findByRefInvocationWalks, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *closure; + zend_class_entry *closureCe = pt_class(PT_CLASS_CLOSURE_EXPR); + if (UNEXPECTED(closureCe == NULL)) RETURN_THROWS(); + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(closure, closureCe) + ZEND_PARSE_PARAMETERS_END(); + ExpressionResultStorage(ZEND_THIS).findByRefInvocationWalks(closure).intoReturnValue(return_value); + }); + cls.shadow(&pt_ce_expression_result_storage); } @@ -203,3 +275,19 @@ bool pt_expression_result_storage_merge_results(zval *storage, zval *other) } /* }}} */ + +zv::Val pt_expression_result_storage_find_by_ref_invocation_walks(zval *storage, zval *closure) +{ + if (EXPECTED(Z_OBJCE_P(storage) == pt_ce_expression_result_storage)) return ExpressionResultStorage(storage).findByRefInvocationWalks(closure); + return pt_type_call(Z_OBJ_P(storage), "findbyrefinvocationwalks", sizeof("findbyrefinvocationwalks") - 1, 1, closure); +} + +[[nodiscard]] bool pt_expression_result_storage_store_by_ref_invocation_walk(zval *storage, zval *closure, zval *walk) +{ + if (EXPECTED(Z_OBJCE_P(storage) == pt_ce_expression_result_storage)) { + ExpressionResultStorage(storage).storeByRefInvocationWalk(closure, walk); + return true; + } + zv::Args argv{closure, walk}; + return !pt_type_call(Z_OBJ_P(storage), "storebyrefinvocationwalk", sizeof("storebyrefinvocationwalk") - 1, 2, argv).isUndef(); +} diff --git a/turbo-ext/src/IssetHandler.cpp b/turbo-ext/src/IssetHandler.cpp index 5838821d25f..92a320f3fe0 100644 --- a/turbo-ext/src/IssetHandler.cpp +++ b/turbo-ext/src/IssetHandler.cpp @@ -575,7 +575,8 @@ class IssetHandler ZVAL_COPY_VALUE(&conjunctionCaptures[1], nodeScopeResolver); ZVAL_COPY_VALUE(&conjunctionCaptures[2], expr); ZVAL_COPY_VALUE(&conjunctionCaptures[3], leftExprNode.raw()); - ZVAL_COPY_VALUE(&conjunctionCaptures[4], accTypes.raw()); + zv::Val leftTypes = memoizeSubjectTypes(accTypes.raw()); + ZVAL_COPY_VALUE(&conjunctionCaptures[4], leftTypes.raw()); ZVAL_COPY_VALUE(&conjunctionCaptures[5], accTruthyScope.raw()); ZVAL_COPY_VALUE(&conjunctionCaptures[6], accFalseyScope.raw()); ZVAL_COPY_VALUE(&conjunctionCaptures[7], rightExprNode.raw()); @@ -675,6 +676,58 @@ class IssetHandler ZVAL_COPY(return_value, &captures[0]); } + /* Mirrors memoizeSubjectTypes(): the closure answering each (scope, + * context) pair once; captures: $types, &$answered */ + static zv::Val memoizeSubjectTypes(zval *types) + { + zval answered; + array_init(&answered); + zval reference; + ZVAL_NEW_REF(&reference, &answered); + zval captures[2]; + ZVAL_COPY_VALUE(&captures[0], types); + ZVAL_COPY_VALUE(&captures[1], &reference); + zv::Val closure = pt_native_closure_new(&memoizedSubjectTypesBody, 2, captures, 1u << 1); + zval_ptr_dtor(&reference); + return closure; + } + + static void memoizedSubjectTypesBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) + { + if (UNEXPECTED(!requireArguments(argc, 2, pt_ish_closure_name))) return; + zval *answered = Z_REFVAL(captures[1]); + if (Z_TYPE_P(answered) == IS_ARRAY && Z_TYPE(argv[0]) == IS_OBJECT && Z_TYPE(argv[1]) == IS_OBJECT) { + zval *entry; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(answered), entry) { + zval *answeredScope = zend_hash_index_find(Z_ARRVAL_P(entry), 0); + zval *answeredContext = zend_hash_index_find(Z_ARRVAL_P(entry), 1); + zval *answer = zend_hash_index_find(Z_ARRVAL_P(entry), 2); + if (answeredScope == NULL || answeredContext == NULL || answer == NULL) continue; + if (Z_OBJ_P(answeredScope) == Z_OBJ(argv[0]) && Z_OBJ_P(answeredContext) == Z_OBJ(argv[1])) { + ZVAL_COPY(return_value, answer); + return; + } + } ZEND_HASH_FOREACH_END(); + } + zv::Val answer = pt_type_call_callable(&captures[0], 2, argv); + if (UNEXPECTED(answer.isUndef())) return; + if (Z_TYPE_P(answered) != IS_ARRAY) { + zval_ptr_dtor(answered); + array_init(answered); + } + SEPARATE_ARRAY(answered); + zval triple; + array_init_size(&triple, 3); + Z_TRY_ADDREF(argv[0]); + zend_hash_next_index_insert(Z_ARRVAL(triple), &argv[0]); + Z_TRY_ADDREF(argv[1]); + zend_hash_next_index_insert(Z_ARRVAL(triple), &argv[1]); + Z_TRY_ADDREF_P(answer.raw()); + zend_hash_next_index_insert(Z_ARRVAL(triple), answer.raw()); + zend_hash_next_index_insert(Z_ARRVAL_P(answered), &triple); + answer.intoReturnValue(return_value); + } + /* fn (MutatingScope $scope, TypeSpecifierContext $ctx): SpecifiedTypes => * $this->booleanNarrowingHelper->specifyConjunction(...) — captures: * $this, $nodeScopeResolver, $expr, $leftExprNode, $leftTypes, diff --git a/turbo-ext/src/StatementsHandler.cpp b/turbo-ext/src/StatementsHandler.cpp index cd0cbe3f9ba..520182d5a9f 100644 --- a/turbo-ext/src/StatementsHandler.cpp +++ b/turbo-ext/src/StatementsHandler.cpp @@ -48,6 +48,10 @@ zend_class_entry *pt_ce_statements_handler = nullptr; /* NodeScopeResolver::LOOP_SCOPE_ITERATIONS / ::GENERALIZE_AFTER_ITERATION */ #define PT_SH_LOOP_SCOPE_ITERATIONS_LIMIT 3 #define PT_SH_GENERALIZE_AFTER_ITERATION_LIMIT 1 +/* the twin's STATIC_WALK_* kinds of the last static-variable walk's log */ +#define PT_SH_STATIC_WALK_WALKED 0 +#define PT_SH_STATIC_WALK_CARRIED 1 +#define PT_SH_STATIC_WALK_REST 2 namespace { @@ -1391,6 +1395,7 @@ class StatementsHandler zv::Val staticVariableTypes = zv::Val(zv::Arr::empty()); zv::Val staticVariableConditionalExpressions = zv::Val(zv::Arr::empty()); zv::Arr staticStatementIndexes = zv::Arr::empty(); + zv::Val staticWalk = zv::Val::null(); if (staticSites.ref().isArray() && zend_hash_num_elements(Z_ARRVAL_P(staticSites.raw())) > 0) { for (auto entry : zv::ArrRef(staticSites.raw())) { zval *index = zend_hash_index_find(Z_ARRVAL_P(entry.value().deref().raw()), 1); @@ -1405,6 +1410,7 @@ class StatementsHandler staticVariableTypes = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(inferred.raw()), 0))); staticVariableConditionalExpressions = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(inferred.raw()), 1))); finalConstraints = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(inferred.raw()), 2))); + staticWalk = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(inferred.raw()), 3))); } zv::Val frame = templateArgumentResolverResolve(OBJ_PROP_NUM(self, slots::templateArgumentResolver), finalConstraints.raw(), parentFrame.raw(), statementStartTokenPositions.raw(), parentNode, stmts); if (UNEXPECTED(frame.isUndef())) return zv::Val(); @@ -1412,6 +1418,18 @@ class StatementsHandler frame = pt_template_argument_frame_with_static_variable_types(frame.raw(), staticVariableTypes.raw(), staticStatementIndexes.raw(), staticVariableConditionalExpressions.raw()); if (UNEXPECTED(frame.isUndef())) return zv::Val(); } + if (staticWalk.ref().isArray()) { + bool constraintsEmpty; + if (UNEXPECTED(!pt_template_argument_constraints_is_empty(finalConstraints.raw(), constraintsEmpty))) return zv::Val(); + if (constraintsEmpty) { + bool hasFunctionLike; + zend_long start = zval_get_long(zend_hash_index_find(Z_ARRVAL_P(staticWalk.raw()), 0)); + if (UNEXPECTED(!pt_static_variable_inference_has_function_like_from(OBJ_PROP_NUM(self, slots::staticVariableInference), parentNode, stmts, start, hasFunctionLike))) return zv::Val(); + if (!hasFunctionLike) { + return replayStaticVariableWalk(nodeScopeResolver, stmts, entries.raw(), recording.raw(), staticWalk.raw(), frame.raw(), parentFrame.raw(), parentConstraints.raw(), storage, nodeCallback, scope.raw(), statsEnabled); + } + } + } { bool observingClosures; if (UNEXPECTED(!pt_template_argument_frame_is_observing_closures(frame.raw(), observingClosures))) return zv::Val(); @@ -1635,6 +1653,7 @@ class StatementsHandler zend_long count = 0; zend_long conditionalExpressionsCount = 0; zv::Val constraints; + zv::Val lastWalk; for (;;) { zv::Val siteTypes = staticSiteTypes(staticSites, types.raw()); if (UNEXPECTED(siteTypes.isUndef())) return zv::Val(); @@ -1659,6 +1678,7 @@ class StatementsHandler if (UNEXPECTED(walkRecording.isUndef())) return zv::Val(); zv::Arr replayedScopes = zv::Arr::empty(); zv::Arr carriedOverScopes = zv::Arr::empty(); + zv::Arr walkLog = zv::Arr::empty(); zv::Val suspendedGatherers = pt_node_scope_resolver_suspend_node_gatherers(nodeScopeResolver); if (UNEXPECTED(suspendedGatherers.isUndef())) return zv::Val(); zv::Val pushedScope = zv::Val::copyOf(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope))); @@ -1666,7 +1686,7 @@ class StatementsHandler (void) pt_node_scope_resolver_restore_node_gatherers(nodeScopeResolver, suspendedGatherers.raw()); return zv::Val(); } - (void) observeStaticVariablesFrom(nodeScopeResolver, parentNode, stmts, stmtsTable, frame.raw(), entries, state.raw(), walkStorage.raw(), walkRecording.raw(), context, start, stmtCount, hasLabels, replayedScopes, carriedOverScopes); + (void) observeStaticVariablesFrom(nodeScopeResolver, parentNode, stmts, stmtsTable, frame.raw(), entries, state.raw(), walkStorage.raw(), walkRecording.raw(), context, start, stmtCount, hasLabels, replayedScopes, carriedOverScopes, walkLog); pt_finally([&]() { (void) pt_mutating_scope_pop_expression_result_storage(Z_OBJ_P(pushedScope.raw())); (void) pt_node_scope_resolver_restore_node_gatherers(nodeScopeResolver, suspendedGatherers.raw()); @@ -1680,6 +1700,15 @@ class StatementsHandler constraints = templateArgumentConstraintsCreateEmpty(); if (UNEXPECTED(constraints.isUndef())) return zv::Val(); } + { + zv::Arr walk = zv::Arr::create(5); + walk.push(zv::Val::integer(start)); + walk.push(zv::Val::copyOf(zv::Ref(state.raw()))); + walk.push(zv::Val::copyOf(zv::Ref(walkRecording.raw()))); + walk.push(zv::Val::copyOf(zv::Ref(walkStorage.raw()))); + walk.push(std::move(walkLog)); + lastWalk = zv::Val(std::move(walk)); + } zv::Val walkTypes = collectStaticVariableTypes(bodyScope.raw(), names.raw(), walkRecording.raw(), state.raw(), replayedScopes.raw()); if (UNEXPECTED(walkTypes.isUndef())) return zv::Val(); @@ -1744,18 +1773,82 @@ class StatementsHandler zv::Val siteTypes = staticSiteTypes(staticSites, types.raw()); if (UNEXPECTED(siteTypes.isUndef())) return zv::Val(); - zv::Arr result = zv::Arr::create(3); + // every way out of the loop leaves the last walk made with the types + // and conditional expressions returned + zv::Arr result = zv::Arr::create(4); result.push(std::move(siteTypes)); result.push(std::move(conditionalExpressions)); result.push(std::move(constraints)); + result.push(std::move(lastWalk)); return zv::Val(std::move(result)); } + /* [kind, a, b] of the last static-variable walk's log */ + static zv::Val staticWalkLogEntry(zend_long kind, zend_long a, zend_long b) + { + zv::Arr entry = zv::Arr::create(3); + entry.push(zv::Val::integer(kind)); + entry.push(zv::Val::integer(a)); + entry.push(zv::Val::integer(b)); + return zv::Val(std::move(entry)); + } + + /* Mirrors the private replayStaticVariableWalk() */ + zv::Val replayStaticVariableWalk(zval *nodeScopeResolver, zval *stmts, zval *entries, zval *recording, zval *staticWalk, zval *frame, zval *parentFrame, zval *parentConstraints, zval *storage, zval *nodeCallback, zval *scope, bool statsEnabled) + { + HashTable *walk = Z_ARRVAL_P(staticWalk); + zend_long start = zval_get_long(zend_hash_index_find(walk, 0)); + zval *state = zend_hash_index_find(walk, 1); + zval *walkRecording = zend_hash_index_find(walk, 2); + zval *walkStorage = zend_hash_index_find(walk, 3); + zval *walkLog = zend_hash_index_find(walk, 4); + if (UNEXPECTED(state == NULL || walkRecording == NULL || walkStorage == NULL || walkLog == NULL)) return zv::Val(); + zend_long stmtCount = zend_hash_num_elements(Z_ARRVAL_P(stmts)); + if (statsEnabled) { + if (UNEXPECTED(!templateArgumentStatsIncrement(PT_LC("bodiesWithSites"), 1))) return zv::Val(); + if (UNEXPECTED(!templateArgumentStatsIncrement(PT_LC("staticVariableWalksReplayed"), 1))) return zv::Val(); + } + zval *startEntry = entryAt(entries, start); + if (UNEXPECTED(startEntry == NULL)) return zv::Val(); + if (UNEXPECTED(!pt_node_scope_resolver_replay_recording_range(nodeScopeResolver, recording, 0, zval_get_long(entryOffset(startEntry)), nodeCallback, storage, scope))) return zv::Val(); + if (UNEXPECTED(!pt_expression_result_storage_merge_results(storage, walkStorage))) return zv::Val(); + zv::Val stateHold = zv::Val::copyOf(zv::Ref(state)); + zv::Val logHold = zv::Val::copyOf(zv::Ref(walkLog)); + for (zv::ArrayEntry entry : zv::ArrRef(logHold.raw())) { + HashTable *op = Z_ARRVAL_P(entry.value().deref().raw()); + zend_long kind = zval_get_long(zend_hash_index_find(op, 0)); + zend_long a = zval_get_long(zend_hash_index_find(op, 1)); + zend_long b = zval_get_long(zend_hash_index_find(op, 2)); + if (kind == PT_SH_STATIC_WALK_WALKED) { + if (UNEXPECTED(!pt_node_scope_resolver_replay_recording_range(nodeScopeResolver, walkRecording, a, b, nodeCallback, storage, scope))) return zv::Val(); + continue; + } + zval *entryPair = entryAt(entries, a); + if (UNEXPECTED(entryPair == NULL)) return zv::Val(); + if (kind == PT_SH_STATIC_WALK_CARRIED) { + zval *exitPair = entryAt(entries, a + 1); + if (UNEXPECTED(exitPair == NULL)) return zv::Val(); + if (UNEXPECTED(!pt_node_scope_resolver_replay_recording_range(nodeScopeResolver, recording, zval_get_long(entryOffset(entryPair)), zval_get_long(entryOffset(exitPair)), nodeCallback, storage, scope))) return zv::Val(); + continue; + } + zend_long recorded; + if (UNEXPECTED(!recordingNodeCallbackCount(recording, recorded))) return zv::Val(); + if (UNEXPECTED(!pt_node_scope_resolver_replay_recording_range(nodeScopeResolver, recording, zval_get_long(entryOffset(entryPair)), recorded, nodeCallback, storage, scope))) return zv::Val(); + // the recorded end scope, as the second pass takes it + zval *finalPair = entryAt(entries, stmtCount); + if (UNEXPECTED(finalPair == NULL)) return zv::Val(); + zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(stateHold.raw()), stateSlots::scope)).assign(zv::Val::copyOf(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(entryState(finalPair)), stateSlots::scope)))); + } + if (UNEXPECTED(!processDeferredByRefClosureBodies(nodeScopeResolver, frame, stateHold.raw(), storage, nodeCallback))) return zv::Val(); + if (UNEXPECTED(!restoreParentFrame(stateHold.raw(), parentFrame, parentConstraints))) return zv::Val(); + return pt_statement_list_walk_state_to_result(stateHold.raw()); + } + /* inferStaticVariableTypes()'s try block: the statements from $start on, * walked or carried over, each walked exit scope and carried-over entry * scope into $replayedScopes, each carried-over entry scope into * $carriedOverScopes; false = pending exception */ - bool observeStaticVariablesFrom(zval *nodeScopeResolver, zval *parentNode, zval *stmts, HashTable *stmtsTable, zval *frame, zval *entries, zval *state, zval *storage, zval *nodeCallback, zval *context, zend_long start, zend_long stmtCount, bool hasLabels, zv::Arr &replayedScopes, zv::Arr &carriedOverScopes) + bool observeStaticVariablesFrom(zval *nodeScopeResolver, zval *parentNode, zval *stmts, HashTable *stmtsTable, zval *frame, zval *entries, zval *state, zval *storage, zval *nodeCallback, zval *context, zend_long start, zend_long stmtCount, bool hasLabels, zv::Arr &replayedScopes, zv::Arr &carriedOverScopes, zv::Arr &walkLog) { for (zend_long i = start; i < stmtCount; i++) { zval *recordedEntryPair = entryAt(entries, i); @@ -1781,7 +1874,9 @@ class StatementsHandler zv::Val carried = withRecordedConstraints(OBJ_PROP_NUM(stateObject, stateSlots::scope), OBJ_PROP_NUM(recordedEntryObject, stateSlots::scope), OBJ_PROP_NUM(Z_OBJ_P(finalState), stateSlots::scope)); if (UNEXPECTED(carried.isUndef())) return false; zv::Ref(OBJ_PROP_NUM(stateObject, stateSlots::scope)).assign(std::move(carried)); - return appendRecordedStatementResults(state, recordedEntry, finalState); + if (UNEXPECTED(!appendRecordedStatementResults(state, recordedEntry, finalState))) return false; + walkLog.push(staticWalkLogEntry(PT_SH_STATIC_WALK_REST, i, 0)); + return true; } } @@ -1795,13 +1890,19 @@ class StatementsHandler if (!reWalk && UNEXPECTED(!frameOwnsSiteInStatement(frame, i, reWalk))) return false; if (!reWalk && UNEXPECTED(!statementMentionsAnyVariable(stmt, differingRoots.raw(), reWalk))) return false; if (reWalk) { + zend_long from; + if (UNEXPECTED(!recordingNodeCallbackCount(nodeCallback, from))) return false; if (UNEXPECTED(!processStatementStep(nodeScopeResolver, parentNode, stmts, i, stmt, state, storage, nodeCallback, context, true))) return false; + zend_long to; + if (UNEXPECTED(!recordingNodeCallbackCount(nodeCallback, to))) return false; + walkLog.push(staticWalkLogEntry(PT_SH_STATIC_WALK_WALKED, from, to)); replayedScopes.push(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope))); continue; } // the statement does not read what changed: the variables keep // their types through it + walkLog.push(staticWalkLogEntry(PT_SH_STATIC_WALK_CARRIED, i, 0)); replayedScopes.push(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope))); carriedOverScopes.push(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope))); zval *recordedExitPair = entryAt(entries, i + 1); @@ -3075,6 +3176,9 @@ PT_MINIT_REGISTRATION(pt_register_statements_handler) { reg::Class cls("PHPStan\\Analyser\\StatementsHandler"); ptdecl::StatementsHandler::declareClass(cls); + cls.privateClassConstantLong("STATIC_WALK_WALKED", PT_SH_STATIC_WALK_WALKED); + cls.privateClassConstantLong("STATIC_WALK_CARRIED", PT_SH_STATIC_WALK_CARRIED); + cls.privateClassConstantLong("STATIC_WALK_REST", PT_SH_STATIC_WALK_REST); cls.privateClassConstantString("MENTIONED_VARIABLES_ATTRIBUTE", "templateArgumentMentionedVariables"); ptdecl::StatementsHandler::declareProperties(cls); diff --git a/turbo-ext/src/StaticVariableInference.cpp b/turbo-ext/src/StaticVariableInference.cpp index 695acf66ba4..7f7d38f6c4a 100644 --- a/turbo-ext/src/StaticVariableInference.cpp +++ b/turbo-ext/src/StaticVariableInference.cpp @@ -29,6 +29,8 @@ namespace { /* the twin's SITES_ATTRIBUTE, a permanent interned string (module startup) */ zend_string *pt_svi_sites_attribute = nullptr; +/* the twin's LAST_FUNCTION_LIKE_ATTRIBUTE, a permanent interned string (module startup) */ +zend_string *pt_svi_last_function_like_attribute = nullptr; pt_method_site pt_svi_get_sub_node_names_site; pt_method_site pt_svi_to_lower_string_site; @@ -325,6 +327,32 @@ zv::Val scanSites(zval *stmts) return zv::Val(std::move(sites)); } +/* Mirrors the private static findLastFunctionLikeStatement(); false = + * pending exception */ +[[nodiscard]] bool findLastFunctionLikeStatement(zval *stmts, zend_long &last) +{ + last = -1; + if (Z_TYPE_P(stmts) != IS_ARRAY) return true; + for (auto stmtEntry : zv::ArrRef(stmts)) { + zend_long index = stmtEntry.stringKeyOrNull() == NULL ? (zend_long) stmtEntry.indexKey() : 0; + zv::Arr stack = zv::Arr::create(0); + stack.push(zv::Val::copyOf(stmtEntry.value().deref())); + for (;;) { + zv::Val node = popNode(stack); + if (node.isUndef()) break; + bool is; + if (UNEXPECTED(!isA(node.raw(), PT_CLASS_FUNCTION_LIKE, is))) return false; + if (!is && UNEXPECTED(!isA(node.raw(), PT_CLASS_CLASS_LIKE_STMT, is))) return false; + if (is) { + last = index; + break; + } + if (UNEXPECTED(!pushSubNodes(node.raw(), stack))) return false; + } + } + return true; +} + } // namespace namespace phpstanturbo { @@ -360,6 +388,24 @@ class StaticVariableInference return sites; } + /* Mirrors hasFunctionLikeFrom(); false = pending exception */ + [[nodiscard]] bool hasFunctionLikeFrom(zval *functionLike, zval *stmts, zend_long index, bool &out) const + { + zv::Val cached = pt_engine_node_get_attribute(Z_OBJ_P(functionLike), ZSTR_VAL(pt_svi_last_function_like_attribute), ZSTR_LEN(pt_svi_last_function_like_attribute)); + if (UNEXPECTED(cached.isUndef())) return false; + zend_long last; + if (cached.isNull()) { + if (UNEXPECTED(!findLastFunctionLikeStatement(stmts, last))) return false; + zval lastZv; + ZVAL_LONG(&lastZv, last); + if (UNEXPECTED(!pt_engine_node_set_attribute(Z_OBJ_P(functionLike), ZSTR_VAL(pt_svi_last_function_like_attribute), ZSTR_LEN(pt_svi_last_function_like_attribute), &lastZv))) return false; + } else { + last = zval_get_long(cached.raw()); + } + out = last >= index; + return true; + } + /* Mirrors isInferred(); false = pending exception */ [[nodiscard]] bool isInferred(zval *scope, zval *var, bool &out) const { @@ -546,6 +592,18 @@ zv::Val pt_static_variable_inference_get_sites(zval *inference, zval *functionLi return pt_type_call(Z_OBJ_P(inference), PT_LC("getsites"), 2, argv); } +bool pt_static_variable_inference_has_function_like_from(zval *inference, zval *functionLike, zval *stmts, zend_long index, bool &out) +{ + if (isNative(inference)) return StaticVariableInference(Z_OBJ_P(inference)).hasFunctionLikeFrom(functionLike, stmts, index, out); + zval indexZv; + ZVAL_LONG(&indexZv, index); + zv::Args argv{functionLike, stmts, &indexZv}; + zv::Val result = pt_type_call(Z_OBJ_P(inference), PT_LC("hasfunctionlikefrom"), 3, argv); + if (UNEXPECTED(result.isUndef())) return false; + out = zend_is_true(result.raw()); + return true; +} + bool pt_static_variable_inference_is_inferred(zval *inference, zval *scope, zval *var, bool &out) { if (isNative(inference)) return StaticVariableInference(Z_OBJ_P(inference)).isInferred(scope, var, out); @@ -596,11 +654,13 @@ bool pt_static_variable_inference_can_run_user_code(zval *inference, zval *node, PT_MINIT_REGISTRATION(pt_register_static_variable_inference) { pt_svi_sites_attribute = zend_string_init_interned(PT_LC("staticVariableInferenceSites"), 1); + pt_svi_last_function_like_attribute = zend_string_init_interned(PT_LC("staticVariableInferenceLastFunctionLike"), 1); reg::Class cls("PHPStan\\Analyser\\Generics\\StaticVariableInference"); ptdecl::StaticVariableInference::declareClass(cls); ptdecl::StaticVariableInference::declareProperties(cls); cls.privateClassConstantString("SITES_ATTRIBUTE", "staticVariableInferenceSites"); + cls.privateClassConstantString("LAST_FUNCTION_LIKE_ATTRIBUTE", "staticVariableInferenceLastFunctionLike"); /* the real parameter types: the DI container autowires the service by * reflecting the constructor */ @@ -646,6 +706,19 @@ PT_MINIT_REGISTRATION(pt_register_static_variable_inference) PT_RETURN_VAL(StaticVariableInference(Z_OBJ_P(ZEND_THIS)).getSites(functionLike, stmts)); }); + cls.method(sigs::hasFunctionLikeFrom, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *functionLike, *stmts; + zend_long index; + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_OBJECT(functionLike) + Z_PARAM_ARRAY(stmts) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + bool out; + if (UNEXPECTED(!StaticVariableInference(Z_OBJ_P(ZEND_THIS)).hasFunctionLikeFrom(functionLike, stmts, index, out))) RETURN_THROWS(); + RETURN_BOOL(out); + }); + cls.method(sigs::isInferred, [](INTERNAL_FUNCTION_PARAMETERS) { zval *scope, *var; if (!zp::parse(execute_data, scope, var)) RETURN_THROWS(); diff --git a/turbo-ext/src/TemplateArgumentObserver.cpp b/turbo-ext/src/TemplateArgumentObserver.cpp index a8d1026c7f5..198de1c0a11 100644 --- a/turbo-ext/src/TemplateArgumentObserver.cpp +++ b/turbo-ext/src/TemplateArgumentObserver.cpp @@ -343,6 +343,11 @@ class TemplateArgumentObserver if (UNEXPECTED(constraints.isUndef())) return zv::Val(); zv::Val declaredValue = pt_type_op(Z_OBJ_P(declared), PT_OP_GET_ITERABLE_VALUE_TYPE, 0, NULL); if (UNEXPECTED(declaredValue.isUndef())) return zv::Val(); + bool plainMixed; + if (UNEXPECTED(!isPlainMixed(declaredValue.raw(), plainMixed))) return zv::Val(); + if (plainMixed) { + return escapeClosures(std::move(constraints), actual); + } zv::Val actualValue = pt_type_op(Z_OBJ_P(actual), PT_OP_GET_ITERABLE_VALUE_TYPE, 0, NULL); if (UNEXPECTED(actualValue.isUndef())) return zv::Val(); return observeClosureSend(std::move(constraints), declaredValue.raw(), actualValue.raw()); @@ -842,17 +847,33 @@ class TemplateArgumentObserver zv::Val declaredKey = pt_type_op(Z_OBJ_P(declared), PT_OP_GET_ITERABLE_KEY_TYPE, 0, NULL); if (UNEXPECTED(declaredKey.isUndef())) return zv::Val(); - zv::Val actualKey = pt_type_op(Z_OBJ_P(actual), PT_OP_GET_ITERABLE_KEY_TYPE, 0, NULL); - if (UNEXPECTED(actualKey.isUndef())) return zv::Val(); - constraints = observeSend(std::move(constraints), declaredKey.raw(), actualKey.raw(), isCallArgument); - if (UNEXPECTED(constraints.isUndef())) return zv::Val(); + bool inert; + if (UNEXPECTED(!isInertSendTarget(declaredKey.raw(), isCallArgument, inert))) return zv::Val(); + if (!inert) { + zv::Val actualKey = pt_type_op(Z_OBJ_P(actual), PT_OP_GET_ITERABLE_KEY_TYPE, 0, NULL); + if (UNEXPECTED(actualKey.isUndef())) return zv::Val(); + constraints = observeSend(std::move(constraints), declaredKey.raw(), actualKey.raw(), isCallArgument); + if (UNEXPECTED(constraints.isUndef())) return zv::Val(); + } zv::Val declaredValue = pt_type_op(Z_OBJ_P(declared), PT_OP_GET_ITERABLE_VALUE_TYPE, 0, NULL); if (UNEXPECTED(declaredValue.isUndef())) return zv::Val(); + if (UNEXPECTED(!isInertSendTarget(declaredValue.raw(), isCallArgument, inert))) return zv::Val(); + if (inert) return constraints; zv::Val actualValue = pt_type_op(Z_OBJ_P(actual), PT_OP_GET_ITERABLE_VALUE_TYPE, 0, NULL); if (UNEXPECTED(actualValue.isUndef())) return zv::Val(); return observeSend(std::move(constraints), declaredValue.raw(), actualValue.raw(), isCallArgument); } + /* Mirrors isInertSendTarget(); false = pending exception */ + [[nodiscard]] static bool isInertSendTarget(zval *declared, bool isCallArgument, bool &out) + { + if (isCallArgument) { + out = false; + return true; + } + return isPlainMixed(declared, out); + } + /* Mirrors observeArgument(). */ static zv::Val observeArgument(zv::Val constraints, zval *parameterType, zval *argumentType) { diff --git a/turbo-ext/src/TypeCombinator.cpp b/turbo-ext/src/TypeCombinator.cpp index 6b0a263ec42..ee155c2085d 100644 --- a/turbo-ext/src/TypeCombinator.cpp +++ b/turbo-ext/src/TypeCombinator.cpp @@ -2449,6 +2449,77 @@ class TypeCombinator return callBool(a, PT_LC("iskeyssupersetof"), 1, b); } + /* Mirrors getConstantScalarValuesByKey(): key => the single constant + * scalar value there, IS_NULL when the array has real unsealed extras; + * UNDEF = pending exception */ + static zv::Val constantScalarValuesByKey(zval *array) + { + zv::Val unsealed = call(array, PT_LC("getunsealedtypes")); + PT_FAIL_IF_UNDEF(unsealed); + if (!isNull(unsealed)) { + zval *unsealedKey = indexOf(unsealed.raw(), 0); + if (UNEXPECTED(unsealedKey == NULL)) { + zend_throw_error(NULL, "Undefined array key 0"); + return zv::Val(); + } + int explicitNever = isExplicitNever(unsealedKey); + PT_FAIL_IF_NEG(explicitNever); + if (!explicitNever) return zv::Val::null(); + } + + zv::Arr values = zv::Arr::create(0); + zv::Val valueTypes = call(array, PT_LC("getvaluetypes")); + PT_FAIL_IF_UNDEF(valueTypes); + zv::Val keyTypes = call(array, PT_LC("getkeytypes")); + PT_FAIL_IF_UNDEF(keyTypes); + for (zv::ArrayEntry keyEntry : zv::ArrRef(keyTypes.raw())) { + zval *valueType = zend_hash_index_find(Z_ARRVAL_P(valueTypes.raw()), keyEntry.indexKey()); + if (UNEXPECTED(valueType == NULL)) { + zend_throw_error(NULL, "Undefined array key " ZEND_ULONG_FMT, keyEntry.indexKey()); + return zv::Val(); + } + zend_long isConstantScalar = callTrinary(valueType, PT_LC("isconstantscalarvalue")); + PT_FAIL_IF_NEG(isConstantScalar); + if (isConstantScalar != PT_TRI_YES) continue; + zv::Val constantScalarValues = call(valueType, PT_LC("getconstantscalarvalues")); + PT_FAIL_IF_UNDEF(constantScalarValues); + if (countOf(constantScalarValues.raw()) != 1) continue; + zv::Val keyValue = call(keyEntry.value().raw(), PT_LC("getvalue")); + PT_FAIL_IF_UNDEF(keyValue); + zval value; + ZVAL_COPY(&value, indexOf(constantScalarValues.raw(), 0)); + if (UNEXPECTED(!symtableSet(values.table(), keyValue.raw(), &value))) return zv::Val(); + } + return zv::Val(std::move(values)); + } + + /* Mirrors haveDisjointConstantScalarValues() */ + static bool haveDisjointConstantScalarValues(zval *a, zval *b, bool oneUnrelatedKeyAllowed) + { + if (a == NULL || b == NULL || Z_TYPE_P(a) != IS_ARRAY || Z_TYPE_P(b) != IS_ARRAY) return false; + if (zend_hash_num_elements(Z_ARRVAL_P(a)) > zend_hash_num_elements(Z_ARRVAL_P(b))) std::swap(a, b); + int differing = 0; + zend_ulong index; + zend_string *key; + zval *value; + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(a), index, key, value) { + zval *other = key != NULL ? zend_hash_find(Z_ARRVAL_P(b), key) : zend_hash_index_find(Z_ARRVAL_P(b), index); + if (other == NULL || zend_is_identical(other, value)) continue; + differing++; + if (differing >= 2 || !oneUnrelatedKeyAllowed) return true; + } ZEND_HASH_FOREACH_END(); + return false; + } + + /* $constantScalarValues[$index] = null */ + static void forgetConstantScalarValues(zv::Arr &constantScalarValues, zend_ulong index) + { + zval null; + ZVAL_NULL(&null); + constantScalarValues.separate(); + zend_hash_index_update(constantScalarValues.table(), index, &null); + } + /* private static reduceArrays(list $constantArrays, bool $preserveTaggedUnions): list */ static zv::Val reduceArrays(zval *constantArrays, bool preserveTaggedUnions) { @@ -2512,6 +2583,19 @@ class TypeCombinator } } + /* the single constant scalar value at each key of the arrays without + * real unsealed extras */ + zv::Arr constantScalarValues = zv::Arr::create(0); + if (preserveTaggedUnions) { + for (zv::ArrayEntry entry : zv::ArrRef(arraysToProcess.raw())) { + zv::Val values = constantScalarValuesByKey(entry.value().raw()); + PT_FAIL_IF_UNDEF(values); + zval v = values.take(); + constantScalarValues.separate(); + zend_hash_index_update(constantScalarValues.table(), entry.indexKey(), &v); + } + } + zv::Arr eligibleCombinations = zv::Arr::create(0); for (zv::ArrayEntry entry : zv::ArrRef(arraysToProcessPerKey.raw())) { HashTable *arrays = Z_ARRVAL_P(entry.value().raw()); @@ -2571,27 +2655,39 @@ class TypeCombinator PT_FAIL_IF_UNDEF(unionValueType); if (!isInstance(unionValueType.raw(), pt_ce_union_type)) { if (UNEXPECTED(!mergeInto(arraysToProcess, j, i))) return zv::Val(); + forgetConstantScalarValues(constantScalarValues, j); break; } } } + zend_long jKeysCount = keyTypesCount(arrayAt(arraysToProcess, j)); + PT_FAIL_IF_NEG(jKeysCount); + if (preserveTaggedUnions + && haveDisjointConstantScalarValues( + zend_hash_index_find(constantScalarValues.table(), i), + zend_hash_index_find(constantScalarValues.table(), j), + iKeysCount == jKeysCount && iKeysCount >= 2)) { + /* neither array is a keys-superset of the other */ + continue; + } + if (preserveTaggedUnions && overlappingKeysCount == iKeysCount) { int superset = isKeysSupersetOf(arrayAt(arraysToProcess, j), arrayAt(arraysToProcess, i)); PT_FAIL_IF_NEG(superset); if (superset) { if (UNEXPECTED(!mergeInto(arraysToProcess, j, i))) return zv::Val(); + forgetConstantScalarValues(constantScalarValues, j); break; } } - zend_long jKeysCount = keyTypesCount(arrayAt(arraysToProcess, j)); - PT_FAIL_IF_NEG(jKeysCount); if (preserveTaggedUnions && overlappingKeysCount == jKeysCount) { int superset = isKeysSupersetOf(arrayAt(arraysToProcess, i), arrayAt(arraysToProcess, j)); PT_FAIL_IF_NEG(superset); if (superset) { if (UNEXPECTED(!mergeInto(arraysToProcess, i, j))) return zv::Val(); + forgetConstantScalarValues(constantScalarValues, i); continue; } } @@ -3070,6 +3166,12 @@ class TypeCombinator return call(newArray.raw(), PT_LC("getarray")); } + /* Mirrors isPlainOrBenevolentUnion() */ + static bool isPlainOrBenevolentUnion(zval *type) + { + return Z_OBJCE_P(type) == pt_ce_union_type || Z_OBJCE_P(type) == pt_ce_benevolent_union_type; + } + static zv::Val doIntersect(uint32_t argc, zval *argv) { size_t typesCount = argc; @@ -3093,13 +3195,20 @@ class TypeCombinator unionTypesCount++; } - /* Fast path: the intersection of two plain unions whose members are - * all finite, mutually-disjoint values is their identity-keyed set - * intersection; restricted to the exact UnionType class */ - if (typesCount == 2 && Z_OBJCE_P(types[0].raw()) == pt_ce_union_type && Z_OBJCE_P(types[1].raw()) == pt_ce_union_type) { - zv::Val finiteIntersection = intersectFiniteUnions(types[0].raw(), types[1].raw()); + /* Fast path: the intersection of two unions whose members are all + * finite, mutually-disjoint values is their identity-keyed set + * intersection; a benevolent union is the one the distribution walks + * first and the benevolence it gives the result is kept */ + if (typesCount == 2 && isPlainOrBenevolentUnion(types[0].raw()) && isPlainOrBenevolentUnion(types[1].raw())) { + zval *a = types[0].raw(); + zval *b = types[1].raw(); + if (Z_OBJCE_P(b) == pt_ce_benevolent_union_type && Z_OBJCE_P(a) != pt_ce_benevolent_union_type) std::swap(a, b); + zv::Val finiteIntersection = intersectFiniteUnions(a, b); PT_FAIL_IF_UNDEF(finiteIntersection); - if (!isNull(finiteIntersection)) return finiteIntersection; + if (!isNull(finiteIntersection)) { + if (Z_OBJCE_P(a) == pt_ce_benevolent_union_type) return pt_union_to_benevolent(finiteIntersection.raw()); + return finiteIntersection; + } } if (unionTypesCount >= 2) { diff --git a/turbo-ext/src/generated/ClosureProcessor.h b/turbo-ext/src/generated/ClosureProcessor.h index c382e861001..e0f9c493e0c 100644 --- a/turbo-ext/src/generated/ClosureProcessor.h +++ b/turbo-ext/src/generated/ClosureProcessor.h @@ -79,27 +79,30 @@ inline constexpr char strings[] = "creationScope\0" /* 963 */ "untilFixpoint\0" /* 977 */ "processByRefInvocation\0" /* 991 */ - "byRefEntryTypes\0" /* 1014 */ - "processDeferredByRefClosureBody\0" /* 1030 */ - "callableParameters\0" /* 1062 */ - "enterWithCapturedUses\0" /* 1081 */ - "source\0" /* 1103 */ - "byRefUses\0" /* 1110 */ - "assignByRefUses\0" /* 1120 */ - "closureScope\0" /* 1136 */ - "gatheredReturnStatementsWithScope\0" /* 1149 */ - "gatheredYieldStatementsWithScope\0" /* 1183 */ - "executionEnds\0" /* 1216 */ - "throwPoints\0" /* 1230 */ - "impurePoints\0" /* 1242 */ - "invalidateExpressions\0" /* 1255 */ - "refineClosureNodeScope\0" /* 1277 */ - "invalidatedExpressions\0" /* 1300 */ - "uses\0" /* 1323 */ - "processImmediatelyCalledCallable\0" /* 1328 */ - "PhpParser\\Node\\Expr\\ArrowFunction\0" /* 1361 */ - "PHPStan\\Analyser\\ProcessArrowFunctionResult\0" /* 1395 */ - "processArrowFunctionNode"; /* 1439 */ + "entryScope\0" /* 1014 */ + "enter\0" /* 1025 */ + "byRefUses\0" /* 1031 */ + "walkByRefInvocation\0" /* 1041 */ + "byRefEntryTypes\0" /* 1061 */ + "processDeferredByRefClosureBody\0" /* 1077 */ + "callableParameters\0" /* 1109 */ + "enterWithCapturedUses\0" /* 1128 */ + "source\0" /* 1150 */ + "assignByRefUses\0" /* 1157 */ + "closureScope\0" /* 1173 */ + "gatheredReturnStatementsWithScope\0" /* 1186 */ + "gatheredYieldStatementsWithScope\0" /* 1220 */ + "executionEnds\0" /* 1253 */ + "throwPoints\0" /* 1267 */ + "impurePoints\0" /* 1279 */ + "invalidateExpressions\0" /* 1292 */ + "refineClosureNodeScope\0" /* 1314 */ + "invalidatedExpressions\0" /* 1337 */ + "uses\0" /* 1360 */ + "processImmediatelyCalledCallable\0" /* 1365 */ + "PhpParser\\Node\\Expr\\ArrowFunction\0" /* 1398 */ + "PHPStan\\Analyser\\ProcessArrowFunctionResult\0" /* 1432 */ + "processArrowFunctionNode"; /* 1476 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 10), /* __construct $container */ reg::packed(48, 0, 72), /* __construct $expressionResultFactory */ @@ -137,47 +140,56 @@ inline constexpr reg::PackedArg args[] = { reg::packed(963, 0, 649), /* processByRefInvocation $creationScope */ reg::packed(977, MAY_BE_BOOL), /* processByRefInvocation $untilFixpoint */ reg::packed(471, MAY_BE_ARRAY), /* processByRefInvocation return */ + reg::packed(532, 0, 550), /* walkByRefInvocation $nodeScopeResolver */ + reg::packed(610, 0, 615), /* walkByRefInvocation $expr */ + reg::packed(643, 0, 649), /* walkByRefInvocation $scope */ + reg::packed(680, 0, 688), /* walkByRefInvocation $storage */ + reg::packed(1014, 0, 649), /* walkByRefInvocation $entryScope */ + reg::packed(1025, MAY_BE_CALLABLE), /* walkByRefInvocation $enter */ + reg::packed(1031, MAY_BE_ARRAY), /* walkByRefInvocation $byRefUses */ + reg::packed(977, MAY_BE_BOOL), /* walkByRefInvocation $untilFixpoint */ + reg::packed(471, MAY_BE_ARRAY), /* walkByRefInvocation return */ reg::packed(532, 0, 550), /* processDeferredByRefClosureBody $nodeScopeResolver */ reg::packed(610, 0, 615), /* processDeferredByRefClosureBody $expr */ reg::packed(643, 0, 649), /* processDeferredByRefClosureBody $scope */ reg::packed(680, 0, 688), /* processDeferredByRefClosureBody $storage */ reg::packed(729, MAY_BE_CALLABLE), /* processDeferredByRefClosureBody $nodeCallback */ - reg::packed(1014, MAY_BE_ARRAY), /* processDeferredByRefClosureBody $byRefEntryTypes */ + reg::packed(1061, MAY_BE_ARRAY), /* processDeferredByRefClosureBody $byRefEntryTypes */ reg::packed(471, MAY_BE_VOID), /* processDeferredByRefClosureBody return */ reg::packed(643, 0, 649), /* enterWithCapturedUses $scope */ reg::packed(610, 0, 615), /* enterWithCapturedUses $expr */ - reg::packed(1062, MAY_BE_ARRAY), /* enterWithCapturedUses $callableParameters */ + reg::packed(1109, MAY_BE_ARRAY), /* enterWithCapturedUses $callableParameters */ reg::packed(963, 0, 649), /* enterWithCapturedUses $creationScope */ reg::packed(471, 0, 649), /* enterWithCapturedUses return */ reg::packed(643, 0, 649), /* assignByRefUses $scope */ - reg::packed(1103, 0, 649), /* assignByRefUses $source */ - reg::packed(1110, MAY_BE_ARRAY), /* assignByRefUses $byRefUses */ + reg::packed(1150, 0, 649), /* assignByRefUses $source */ + reg::packed(1031, MAY_BE_ARRAY), /* assignByRefUses $byRefUses */ reg::packed(471, 0, 649), /* assignByRefUses return */ - reg::packed(1136, 0, 649), /* refineClosureNodeScope $closureScope */ + reg::packed(1173, 0, 649), /* refineClosureNodeScope $closureScope */ reg::packed(643, 0, 649), /* refineClosureNodeScope $scope */ reg::packed(610, 0, 615), /* refineClosureNodeScope $expr */ - reg::packed(1149, MAY_BE_ARRAY), /* refineClosureNodeScope $gatheredReturnStatementsWithScope */ - reg::packed(1183, MAY_BE_ARRAY), /* refineClosureNodeScope $gatheredYieldStatementsWithScope */ - reg::packed(1216, MAY_BE_ARRAY), /* refineClosureNodeScope $executionEnds */ - reg::packed(1230, MAY_BE_ARRAY), /* refineClosureNodeScope $throwPoints */ - reg::packed(1242, MAY_BE_ARRAY), /* refineClosureNodeScope $impurePoints */ - reg::packed(1255, MAY_BE_ARRAY), /* refineClosureNodeScope $invalidateExpressions */ + reg::packed(1186, MAY_BE_ARRAY), /* refineClosureNodeScope $gatheredReturnStatementsWithScope */ + reg::packed(1220, MAY_BE_ARRAY), /* refineClosureNodeScope $gatheredYieldStatementsWithScope */ + reg::packed(1253, MAY_BE_ARRAY), /* refineClosureNodeScope $executionEnds */ + reg::packed(1267, MAY_BE_ARRAY), /* refineClosureNodeScope $throwPoints */ + reg::packed(1279, MAY_BE_ARRAY), /* refineClosureNodeScope $impurePoints */ + reg::packed(1292, MAY_BE_ARRAY), /* refineClosureNodeScope $invalidateExpressions */ reg::packed(680, 0, 688), /* refineClosureNodeScope $storage */ reg::packed(471, 0, 649), /* refineClosureNodeScope return */ reg::packed(643, 0, 649), /* processImmediatelyCalledCallable $scope */ - reg::packed(1300, MAY_BE_ARRAY), /* processImmediatelyCalledCallable $invalidatedExpressions */ - reg::packed(1323, MAY_BE_ARRAY), /* processImmediatelyCalledCallable $uses */ + reg::packed(1337, MAY_BE_ARRAY), /* processImmediatelyCalledCallable $invalidatedExpressions */ + reg::packed(1360, MAY_BE_ARRAY), /* processImmediatelyCalledCallable $uses */ reg::packed(471, 0, 649), /* processImmediatelyCalledCallable return */ reg::packed(532, 0, 550), /* processArrowFunctionNode $nodeScopeResolver */ reg::packed(585, 0, 590), /* processArrowFunctionNode $stmt */ - reg::packed(610, 0, 1361), /* processArrowFunctionNode $expr */ + reg::packed(610, 0, 1398), /* processArrowFunctionNode $expr */ reg::packed(643, 0, 649), /* processArrowFunctionNode $scope */ reg::packed(680, 0, 688), /* processArrowFunctionNode $storage */ reg::packed(729, MAY_BE_CALLABLE), /* processArrowFunctionNode $nodeCallback */ reg::packed(785, MAY_BE_NULL, 798), /* processArrowFunctionNode $passedToType */ reg::packed(816, MAY_BE_NULL, 798, false, false, 835), /* processArrowFunctionNode $nativePassedToType */ reg::packed(742, MAY_BE_NULL, 750, false, false, 835), /* processArrowFunctionNode $context */ - reg::packed(471, 0, 1395), /* processArrowFunctionNode return */ + reg::packed(471, 0, 1432), /* processArrowFunctionNode return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -189,12 +201,13 @@ inline constexpr sigtab::Sig getParametersProcessor = { { 509 /* getParametersPr inline constexpr sigtab::Sig processClosureNode = { { 878 /* processClosureNode */, 8, 7, 9, 16, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig processClosureNodeInternal = { { 897 /* processClosureNodeInternal */, 8, 17, 9, 26, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig processByRefInvocation = { { 991 /* processByRefInvocation */, 8, 27, 8, 35, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processDeferredByRefClosureBody = { { 1030 /* processDeferredByRefClosureBody */, 6, 36, 6, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterWithCapturedUses = { { 1081 /* enterWithCapturedUses */, 4, 43, 4, 47, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig assignByRefUses = { { 1120 /* assignByRefUses */, 3, 48, 3, 51, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig refineClosureNodeScope = { { 1277 /* refineClosureNodeScope */, 10, 52, 10, 62, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processImmediatelyCalledCallable = { { 1328 /* processImmediatelyCalledCallable */, 3, 63, 3, 66, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processArrowFunctionNode = { { 1439 /* processArrowFunctionNode */, 7, 67, 9, 76, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig walkByRefInvocation = { { 1041 /* walkByRefInvocation */, 8, 36, 8, 44, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processDeferredByRefClosureBody = { { 1077 /* processDeferredByRefClosureBody */, 6, 45, 6, 51, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterWithCapturedUses = { { 1128 /* enterWithCapturedUses */, 4, 52, 4, 56, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig assignByRefUses = { { 1157 /* assignByRefUses */, 3, 57, 3, 60, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig refineClosureNodeScope = { { 1314 /* refineClosureNodeScope */, 10, 61, 10, 71, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processImmediatelyCalledCallable = { { 1365 /* processImmediatelyCalledCallable */, 3, 72, 3, 75, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processArrowFunctionNode = { { 1476 /* processArrowFunctionNode */, 7, 76, 9, 85, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ClosureProcessor diff --git a/turbo-ext/src/generated/ClosureSignatureInference.h b/turbo-ext/src/generated/ClosureSignatureInference.h index 9d988e3a13f..b3eefcb2851 100644 --- a/turbo-ext/src/generated/ClosureSignatureInference.h +++ b/turbo-ext/src/generated/ClosureSignatureInference.h @@ -57,54 +57,56 @@ inline constexpr char strings[] = "input\0" /* 533 */ "result\0" /* 539 */ "collectAbsorbed\0" /* 546 */ - "types\0" /* 562 */ - "collectAbsorbedInUnion\0" /* 568 */ - "hasMarkers\0" /* 591 */ - "collectMarkers\0" /* 602 */ - "call\0" /* 617 */ - "PhpParser\\Node\\Expr\0" /* 622 */ - "closureType\0" /* 642 */ - "PHPStan\\Type\\ClosureType\0" /* 654 */ - "observing\0" /* 679 */ - "collectInvocation\0" /* 689 */ - "collectByRefEntryTypes\0" /* 707 */ - "PhpParser\\Node\\Expr\\ArrowFunction\0" /* 730 */ - "getArrowFunctionOuterVariables\0" /* 764 */ - "byRefUseNames\0" /* 795 */ - "stmts\0" /* 809 */ - "names\0" /* 815 */ - "bodyYieldsOrInvokes\0" /* 821 */ - "body\0" /* 841 */ - "PhpParser\\Node\0" /* 846 */ - "getReboundVariableNames\0" /* 861 */ - "node\0" /* 885 */ - "collectVariableName\0" /* 890 */ - "target\0" /* 910 */ - "collectByRefItemNames\0" /* 917 */ - "parameterName\0" /* 939 */ - "parameterTemplateName\0" /* 953 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 975 */ - "getFrame\0" /* 1023 */ - "isObserving\0" /* 1032 */ - "declaredParameters\0" /* 1044 */ - "getSignatureParameters\0" /* 1063 */ - "getBodyParameters\0" /* 1086 */ - "returnType\0" /* 1104 */ - "getSignatureReturnType\0" /* 1115 */ - "getExpectedReturnType\0" /* 1138 */ - "collectSites\0" /* 1160 */ - "parameter\0" /* 1173 */ - "PHPStan\\Reflection\\Native\\NativeParameterReflection\0" /* 1183 */ - "createParameterMarker\0" /* 1235 */ - "returnsContextTypedExpression\0" /* 1257 */ - "functionLike\0" /* 1287 */ - "isClosedBody\0" /* 1300 */ - "scanClosedBody\0" /* 1313 */ - "infersInvocationReturnType\0" /* 1328 */ - "findAssignedClosures\0" /* 1355 */ - "getAssignedClosures\0" /* 1376 */ - "collectTargetNames\0" /* 1396 */ - "isContextTyped"; /* 1415 */ + "inputs\0" /* 562 */ + "collectAbsorbedInto\0" /* 569 */ + "types\0" /* 589 */ + "collectAbsorbedInUnion\0" /* 595 */ + "hasMarkers\0" /* 618 */ + "collectMarkers\0" /* 629 */ + "call\0" /* 644 */ + "PhpParser\\Node\\Expr\0" /* 649 */ + "closureType\0" /* 669 */ + "PHPStan\\Type\\ClosureType\0" /* 681 */ + "observing\0" /* 706 */ + "collectInvocation\0" /* 716 */ + "collectByRefEntryTypes\0" /* 734 */ + "PhpParser\\Node\\Expr\\ArrowFunction\0" /* 757 */ + "getArrowFunctionOuterVariables\0" /* 791 */ + "byRefUseNames\0" /* 822 */ + "stmts\0" /* 836 */ + "names\0" /* 842 */ + "bodyYieldsOrInvokes\0" /* 848 */ + "body\0" /* 868 */ + "PhpParser\\Node\0" /* 873 */ + "getReboundVariableNames\0" /* 888 */ + "node\0" /* 912 */ + "collectVariableName\0" /* 917 */ + "target\0" /* 937 */ + "collectByRefItemNames\0" /* 944 */ + "parameterName\0" /* 966 */ + "parameterTemplateName\0" /* 980 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1002 */ + "getFrame\0" /* 1050 */ + "isObserving\0" /* 1059 */ + "declaredParameters\0" /* 1071 */ + "getSignatureParameters\0" /* 1090 */ + "getBodyParameters\0" /* 1113 */ + "returnType\0" /* 1131 */ + "getSignatureReturnType\0" /* 1142 */ + "getExpectedReturnType\0" /* 1165 */ + "collectSites\0" /* 1187 */ + "parameter\0" /* 1200 */ + "PHPStan\\Reflection\\Native\\NativeParameterReflection\0" /* 1210 */ + "createParameterMarker\0" /* 1262 */ + "returnsContextTypedExpression\0" /* 1284 */ + "functionLike\0" /* 1314 */ + "isClosedBody\0" /* 1327 */ + "scanClosedBody\0" /* 1340 */ + "infersInvocationReturnType\0" /* 1355 */ + "findAssignedClosures\0" /* 1382 */ + "getAssignedClosures\0" /* 1403 */ + "collectTargetNames\0" /* 1423 */ + "isContextTyped"; /* 1442 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL), /* __construct $enabled */ reg::packed(20, 0, 27), /* isClosureSignatureMarker $marker */ @@ -136,82 +138,85 @@ inline constexpr reg::PackedArg args[] = { reg::packed(533, 0, 307), /* collectAbsorbed $input */ reg::packed(539, 0, 307), /* collectAbsorbed $result */ reg::packed(79, 0, 410), /* collectAbsorbed return */ - reg::packed(562, MAY_BE_ARRAY), /* collectAbsorbedInUnion $types */ + reg::packed(562, MAY_BE_ARRAY), /* collectAbsorbedInto $inputs */ + reg::packed(539, 0, 307), /* collectAbsorbedInto $result */ + reg::packed(79, 0, 410), /* collectAbsorbedInto return */ + reg::packed(589, MAY_BE_ARRAY), /* collectAbsorbedInUnion $types */ reg::packed(79, 0, 410), /* collectAbsorbedInUnion return */ reg::packed(405, 0, 307), /* hasMarkers $type */ reg::packed(79, MAY_BE_BOOL), /* hasMarkers return */ reg::packed(405, 0, 307), /* collectMarkers $type */ reg::packed(79, MAY_BE_ARRAY), /* collectMarkers return */ reg::packed(134, 0, 140), /* collectInvocation $scope */ - reg::packed(617, 0, 622), /* collectInvocation $call */ - reg::packed(642, 0, 654), /* collectInvocation $closureType */ - reg::packed(679, MAY_BE_BOOL), /* collectInvocation $observing */ + reg::packed(644, 0, 649), /* collectInvocation $call */ + reg::packed(669, 0, 681), /* collectInvocation $closureType */ + reg::packed(706, MAY_BE_BOOL), /* collectInvocation $observing */ reg::packed(79, 0, 410), /* collectInvocation return */ reg::packed(134, 0, 140), /* collectByRefEntryTypes $scope */ reg::packed(79, MAY_BE_ARRAY), /* collectByRefEntryTypes return */ - reg::packed(171, 0, 730), /* getArrowFunctionOuterVariables $expr */ + reg::packed(171, 0, 757), /* getArrowFunctionOuterVariables $expr */ reg::packed(79, MAY_BE_ARRAY), /* getArrowFunctionOuterVariables return */ reg::packed(171, 0, 257), /* byRefUseNames $expr */ reg::packed(79, MAY_BE_ARRAY), /* byRefUseNames return */ - reg::packed(809, MAY_BE_ARRAY), /* bodyYieldsOrInvokes $stmts */ - reg::packed(815, MAY_BE_ARRAY), /* bodyYieldsOrInvokes $names */ + reg::packed(836, MAY_BE_ARRAY), /* bodyYieldsOrInvokes $stmts */ + reg::packed(842, MAY_BE_ARRAY), /* bodyYieldsOrInvokes $names */ reg::packed(79, MAY_BE_BOOL), /* bodyYieldsOrInvokes return */ - reg::packed(841, 0, 846), /* getReboundVariableNames $body */ - reg::packed(809, MAY_BE_ARRAY), /* getReboundVariableNames $stmts */ + reg::packed(868, 0, 873), /* getReboundVariableNames $body */ + reg::packed(836, MAY_BE_ARRAY), /* getReboundVariableNames $stmts */ reg::packed(79, MAY_BE_ARRAY), /* getReboundVariableNames return */ - reg::packed(885, 0, 846), /* collectVariableName $node */ - reg::packed(815, MAY_BE_ARRAY, reg::NoString, true, false), /* collectVariableName $names */ + reg::packed(912, 0, 873), /* collectVariableName $node */ + reg::packed(842, MAY_BE_ARRAY, reg::NoString, true, false), /* collectVariableName $names */ reg::packed(79, MAY_BE_VOID), /* collectVariableName return */ - reg::packed(910, 0, 622), /* collectByRefItemNames $target */ - reg::packed(815, MAY_BE_ARRAY, reg::NoString, true, false), /* collectByRefItemNames $names */ + reg::packed(937, 0, 649), /* collectByRefItemNames $target */ + reg::packed(842, MAY_BE_ARRAY, reg::NoString, true, false), /* collectByRefItemNames $names */ reg::packed(79, MAY_BE_VOID), /* collectByRefItemNames return */ - reg::packed(939, MAY_BE_STRING), /* parameterTemplateName $parameterName */ + reg::packed(966, MAY_BE_STRING), /* parameterTemplateName $parameterName */ reg::packed(79, MAY_BE_STRING), /* parameterTemplateName return */ reg::packed(134, 0, 140), /* getFrame $scope */ - reg::packed(79, MAY_BE_NULL, 975), /* getFrame return */ + reg::packed(79, MAY_BE_NULL, 1002), /* getFrame return */ reg::packed(134, 0, 140), /* isObserving $scope */ reg::packed(79, MAY_BE_BOOL), /* isObserving return */ reg::packed(134, 0, 140), /* getSignatureParameters $scope */ reg::packed(171, 0, 176), /* getSignatureParameters $expr */ - reg::packed(1044, MAY_BE_ARRAY), /* getSignatureParameters $declaredParameters */ + reg::packed(1071, MAY_BE_ARRAY), /* getSignatureParameters $declaredParameters */ reg::packed(79, MAY_BE_ARRAY), /* getSignatureParameters return */ reg::packed(134, 0, 140), /* getBodyParameters $scope */ reg::packed(171, 0, 176), /* getBodyParameters $expr */ reg::packed(79, MAY_BE_NULL | MAY_BE_ARRAY), /* getBodyParameters return */ reg::packed(134, 0, 140), /* getSignatureReturnType $scope */ reg::packed(171, 0, 176), /* getSignatureReturnType $expr */ - reg::packed(1104, 0, 307), /* getSignatureReturnType $returnType */ + reg::packed(1131, 0, 307), /* getSignatureReturnType $returnType */ reg::packed(79, 0, 307), /* getSignatureReturnType return */ reg::packed(134, 0, 140), /* getExpectedReturnType $scope */ reg::packed(171, 0, 176), /* getExpectedReturnType $expr */ reg::packed(79, MAY_BE_NULL, 307), /* getExpectedReturnType return */ reg::packed(134, 0, 140), /* collectSites $scope */ - reg::packed(642, 0, 307), /* collectSites $closureType */ + reg::packed(669, 0, 307), /* collectSites $closureType */ reg::packed(79, 0, 410), /* collectSites return */ reg::packed(171, 0, 176), /* createParameterMarker $expr */ - reg::packed(1173, 0, 1183), /* createParameterMarker $parameter */ + reg::packed(1200, 0, 1210), /* createParameterMarker $parameter */ reg::packed(79, 0, 27), /* createParameterMarker return */ reg::packed(171, 0, 176), /* returnsContextTypedExpression $expr */ reg::packed(79, MAY_BE_BOOL), /* returnsContextTypedExpression return */ - reg::packed(1287, 0, 846), /* isClosedBody $functionLike */ - reg::packed(809, MAY_BE_ARRAY), /* isClosedBody $stmts */ + reg::packed(1314, 0, 873), /* isClosedBody $functionLike */ + reg::packed(836, MAY_BE_ARRAY), /* isClosedBody $stmts */ reg::packed(79, MAY_BE_BOOL), /* isClosedBody return */ - reg::packed(1287, 0, 846), /* scanClosedBody $functionLike */ - reg::packed(809, MAY_BE_ARRAY), /* scanClosedBody $stmts */ + reg::packed(1314, 0, 873), /* scanClosedBody $functionLike */ + reg::packed(836, MAY_BE_ARRAY), /* scanClosedBody $stmts */ reg::packed(79, MAY_BE_BOOL), /* scanClosedBody return */ reg::packed(134, 0, 140), /* infersInvocationReturnType $scope */ - reg::packed(642, 0, 654), /* infersInvocationReturnType $closureType */ + reg::packed(669, 0, 681), /* infersInvocationReturnType $closureType */ reg::packed(79, MAY_BE_BOOL), /* infersInvocationReturnType return */ reg::packed(134, 0, 140), /* findAssignedClosures $scope */ reg::packed(302, MAY_BE_STRING), /* findAssignedClosures $name */ reg::packed(79, MAY_BE_ARRAY), /* findAssignedClosures return */ - reg::packed(841, 0, 846), /* getAssignedClosures $body */ - reg::packed(809, MAY_BE_ARRAY), /* getAssignedClosures $stmts */ + reg::packed(868, 0, 873), /* getAssignedClosures $body */ + reg::packed(836, MAY_BE_ARRAY), /* getAssignedClosures $stmts */ reg::packed(79, MAY_BE_ARRAY), /* getAssignedClosures return */ - reg::packed(910, 0, 622), /* collectTargetNames $target */ - reg::packed(815, MAY_BE_ARRAY, reg::NoString, true, false), /* collectTargetNames $names */ + reg::packed(937, 0, 649), /* collectTargetNames $target */ + reg::packed(842, MAY_BE_ARRAY, reg::NoString, true, false), /* collectTargetNames $names */ reg::packed(79, MAY_BE_VOID), /* collectTargetNames return */ - reg::packed(171, 0, 622), /* isContextTyped $expr */ + reg::packed(171, 0, 649), /* isContextTyped $expr */ reg::packed(79, MAY_BE_BOOL), /* isContextTyped return */ }; using Sig = reg::Sig; @@ -231,34 +236,35 @@ inline constexpr sigtab::Sig collectCaptureEscapes = { { 464 /* collectCaptureEs inline constexpr sigtab::Sig collectEscapes = { { 486 /* collectEscapes */, 1, 23, 1, 24, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig collectInvokedCallee = { { 512 /* collectInvokedCallee */, 1, 25, 1, 26, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig collectAbsorbed = { { 546 /* collectAbsorbed */, 2, 27, 2, 29, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectAbsorbedInUnion = { { 568 /* collectAbsorbedInUnion */, 1, 30, 1, 31, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig hasMarkers = { { 591 /* hasMarkers */, 1, 32, 1, 33, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectMarkers = { { 602 /* collectMarkers */, 1, 34, 1, 35, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectInvocation = { { 689 /* collectInvocation */, 4, 36, 4, 40, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectByRefEntryTypes = { { 707 /* collectByRefEntryTypes */, 1, 41, 1, 42, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getArrowFunctionOuterVariables = { { 764 /* getArrowFunctionOuterVariables */, 1, 43, 1, 44, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig byRefUseNames = { { 795 /* byRefUseNames */, 1, 45, 1, 46, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig bodyYieldsOrInvokes = { { 821 /* bodyYieldsOrInvokes */, 2, 47, 2, 49, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getReboundVariableNames = { { 861 /* getReboundVariableNames */, 2, 50, 2, 52, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectVariableName = { { 890 /* collectVariableName */, 2, 53, 2, 55, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectByRefItemNames = { { 917 /* collectByRefItemNames */, 2, 56, 2, 58, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig parameterTemplateName = { { 953 /* parameterTemplateName */, 1, 59, 1, 60, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getFrame = { { 1023 /* getFrame */, 1, 61, 1, 62, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isObserving = { { 1032 /* isObserving */, 1, 63, 1, 64, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSignatureParameters = { { 1063 /* getSignatureParameters */, 3, 65, 3, 68, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getBodyParameters = { { 1086 /* getBodyParameters */, 2, 69, 2, 71, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getSignatureReturnType = { { 1115 /* getSignatureReturnType */, 3, 72, 3, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getExpectedReturnType = { { 1138 /* getExpectedReturnType */, 2, 76, 2, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig collectSites = { { 1160 /* collectSites */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig createParameterMarker = { { 1235 /* createParameterMarker */, 2, 82, 2, 84, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig returnsContextTypedExpression = { { 1257 /* returnsContextTypedExpression */, 1, 85, 1, 86, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isClosedBody = { { 1300 /* isClosedBody */, 2, 87, 2, 89, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig scanClosedBody = { { 1313 /* scanClosedBody */, 2, 90, 2, 92, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig infersInvocationReturnType = { { 1328 /* infersInvocationReturnType */, 2, 93, 2, 95, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig findAssignedClosures = { { 1355 /* findAssignedClosures */, 2, 96, 2, 98, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getAssignedClosures = { { 1376 /* getAssignedClosures */, 2, 99, 2, 101, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectTargetNames = { { 1396 /* collectTargetNames */, 2, 102, 2, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig isContextTyped = { { 1415 /* isContextTyped */, 1, 105, 1, 106, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectAbsorbedInto = { { 569 /* collectAbsorbedInto */, 2, 30, 2, 32, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectAbsorbedInUnion = { { 595 /* collectAbsorbedInUnion */, 1, 33, 1, 34, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig hasMarkers = { { 618 /* hasMarkers */, 1, 35, 1, 36, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectMarkers = { { 629 /* collectMarkers */, 1, 37, 1, 38, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectInvocation = { { 716 /* collectInvocation */, 4, 39, 4, 43, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectByRefEntryTypes = { { 734 /* collectByRefEntryTypes */, 1, 44, 1, 45, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getArrowFunctionOuterVariables = { { 791 /* getArrowFunctionOuterVariables */, 1, 46, 1, 47, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig byRefUseNames = { { 822 /* byRefUseNames */, 1, 48, 1, 49, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig bodyYieldsOrInvokes = { { 848 /* bodyYieldsOrInvokes */, 2, 50, 2, 52, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getReboundVariableNames = { { 888 /* getReboundVariableNames */, 2, 53, 2, 55, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectVariableName = { { 917 /* collectVariableName */, 2, 56, 2, 58, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectByRefItemNames = { { 944 /* collectByRefItemNames */, 2, 59, 2, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig parameterTemplateName = { { 980 /* parameterTemplateName */, 1, 62, 1, 63, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getFrame = { { 1050 /* getFrame */, 1, 64, 1, 65, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isObserving = { { 1059 /* isObserving */, 1, 66, 1, 67, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSignatureParameters = { { 1090 /* getSignatureParameters */, 3, 68, 3, 71, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getBodyParameters = { { 1113 /* getBodyParameters */, 2, 72, 2, 74, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSignatureReturnType = { { 1142 /* getSignatureReturnType */, 3, 75, 3, 78, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getExpectedReturnType = { { 1165 /* getExpectedReturnType */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig collectSites = { { 1187 /* collectSites */, 2, 82, 2, 84, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig createParameterMarker = { { 1262 /* createParameterMarker */, 2, 85, 2, 87, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig returnsContextTypedExpression = { { 1284 /* returnsContextTypedExpression */, 1, 88, 1, 89, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isClosedBody = { { 1327 /* isClosedBody */, 2, 90, 2, 92, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig scanClosedBody = { { 1340 /* scanClosedBody */, 2, 93, 2, 95, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig infersInvocationReturnType = { { 1355 /* infersInvocationReturnType */, 2, 96, 2, 98, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findAssignedClosures = { { 1382 /* findAssignedClosures */, 2, 99, 2, 101, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAssignedClosures = { { 1403 /* getAssignedClosures */, 2, 102, 2, 104, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectTargetNames = { { 1423 /* collectTargetNames */, 2, 105, 2, 107, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isContextTyped = { { 1442 /* isContextTyped */, 1, 108, 1, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::ClosureSignatureInference diff --git a/turbo-ext/src/generated/ExpressionResultStorage.h b/turbo-ext/src/generated/ExpressionResultStorage.h index 5aa8674d8fe..ca735826a60 100644 --- a/turbo-ext/src/generated/ExpressionResultStorage.h +++ b/turbo-ext/src/generated/ExpressionResultStorage.h @@ -12,6 +12,7 @@ namespace ptdecl::ExpressionResultStorage { namespace slot { inline constexpr uint32_t exprResults = 0; inline constexpr uint32_t fallback = 1; +inline constexpr uint32_t byRefInvocationWalks = 2; } // namespace slot inline void declareClass(reg::Class &cls) @@ -24,6 +25,7 @@ inline void declareProperties(reg::Class &cls) { cls.property("exprResults", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "SplObjectStorage"); cls.property("fallback", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "PHPStan\\Analyser\\ExpressionResultStorage"); + cls.property("byRefInvocationWalks", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "SplObjectStorage"); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -40,7 +42,12 @@ inline constexpr char strings[] = "expressionResult\0" /* 108 */ "PHPStan\\Analyser\\ExpressionResult\0" /* 125 */ "storeExpressionResult\0" /* 159 */ - "findExpressionResult"; /* 181 */ + "findExpressionResult\0" /* 181 */ + "closure\0" /* 202 */ + "PhpParser\\Node\\Expr\\Closure\0" /* 210 */ + "walk\0" /* 238 */ + "storeByRefInvocationWalk\0" /* 243 */ + "findByRefInvocationWalks"; /* 268 */ inline constexpr reg::PackedArg args[] = { reg::packed(12, 0, 13), /* duplicate return */ reg::packed(64, 0, 13), /* mergeResults $other */ @@ -50,6 +57,11 @@ inline constexpr reg::PackedArg args[] = { reg::packed(12, MAY_BE_VOID), /* storeExpressionResult return */ reg::packed(83, 0, 88), /* findExpressionResult $expr */ reg::packed(12, MAY_BE_NULL, 125), /* findExpressionResult return */ + reg::packed(202, 0, 210), /* storeByRefInvocationWalk $closure */ + reg::packed(238, MAY_BE_ARRAY), /* storeByRefInvocationWalk $walk */ + reg::packed(12, MAY_BE_VOID), /* storeByRefInvocationWalk return */ + reg::packed(202, 0, 210), /* findByRefInvocationWalks $closure */ + reg::packed(12, MAY_BE_ARRAY), /* findByRefInvocationWalks return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -61,6 +73,8 @@ inline constexpr sigtab::Sig duplicate = { { 54 /* duplicate */, 0, 0, 0, 0, ZEN inline constexpr sigtab::Sig mergeResults = { { 70 /* mergeResults */, 1, 1, 1, 2, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig storeExpressionResult = { { 159 /* storeExpressionResult */, 2, 3, 2, 5, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig findExpressionResult = { { 181 /* findExpressionResult */, 1, 6, 1, 7, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig storeByRefInvocationWalk = { { 243 /* storeByRefInvocationWalk */, 2, 8, 2, 10, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findByRefInvocationWalks = { { 268 /* findByRefInvocationWalks */, 1, 11, 1, 12, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ExpressionResultStorage diff --git a/turbo-ext/src/generated/IssetHandler.h b/turbo-ext/src/generated/IssetHandler.h index 3cb203bdab8..1f66b94bce5 100644 --- a/turbo-ext/src/generated/IssetHandler.h +++ b/turbo-ext/src/generated/IssetHandler.h @@ -63,7 +63,10 @@ inline constexpr char strings[] = "context\0" /* 613 */ "PHPStan\\Analyser\\ExpressionContext\0" /* 621 */ "PHPStan\\Analyser\\ExpressionResult\0" /* 656 */ - "processExpr"; /* 690 */ + "processExpr\0" /* 690 */ + "types\0" /* 702 */ + "Closure\0" /* 708 */ + "memoizeSubjectTypes"; /* 716 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 21), /* __construct $nonNullabilityHelper */ reg::packed(78, 0, 102), /* __construct $expressionResultFactory */ @@ -80,6 +83,8 @@ inline constexpr reg::PackedArg args[] = { reg::packed(600, MAY_BE_CALLABLE), /* processExpr $nodeCallback */ reg::packed(613, 0, 621), /* processExpr $context */ reg::packed(426, 0, 656), /* processExpr return */ + reg::packed(702, 0, 708), /* memoizeSubjectTypes $types */ + reg::packed(426, 0, 708), /* memoizeSubjectTypes return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -89,6 +94,7 @@ namespace sig { inline constexpr sigtab::Sig __construct = { { 389 /* __construct */, 5, 0, 5, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig supports = { { 427 /* supports */, 1, 5, 1, 6, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig processExpr = { { 690 /* processExpr */, 7, 7, 7, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig memoizeSubjectTypes = { { 716 /* memoizeSubjectTypes */, 1, 15, 1, 16, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::IssetHandler diff --git a/turbo-ext/src/generated/StatementsHandler.h b/turbo-ext/src/generated/StatementsHandler.h index 45e6a03a74b..804e3ef1442 100644 --- a/turbo-ext/src/generated/StatementsHandler.h +++ b/turbo-ext/src/generated/StatementsHandler.h @@ -100,58 +100,61 @@ inline constexpr char strings[] = "recording\0" /* 1242 */ "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1252 */ "inferStaticVariableTypes\0" /* 1291 */ - "bodyScope\0" /* 1316 */ - "endState\0" /* 1326 */ - "moreScopes\0" /* 1335 */ - "collectStaticVariableScopes\0" /* 1346 */ - "collectStaticVariableStateScopes\0" /* 1374 */ - "names\0" /* 1407 */ - "scopes\0" /* 1413 */ - "collectStaticVariableTypes\0" /* 1420 */ - "runs\0" /* 1447 */ - "types\0" /* 1452 */ - "walkedConditionalExpressions\0" /* 1458 */ - "enteredTypes\0" /* 1487 */ - "collectStaticVariableConditionalExpressions\0" /* 1500 */ - "isWalkedStaticVariableState\0" /* 1544 */ - "a\0" /* 1572 */ - "b\0" /* 1574 */ - "equalStaticVariableStates\0" /* 1576 */ - "equalStaticVariableConditionalExpressions\0" /* 1602 */ - "PHPStan\\Analyser\\ConditionalExpressionHolder\0" /* 1644 */ - "equalConditionalExpressionHolders\0" /* 1689 */ - "walkTypes\0" /* 1723 */ - "generalize\0" /* 1733 */ - "joinStaticVariableTypes\0" /* 1744 */ - "PHPStan\\Analyser\\ClosureProcessor\0" /* 1768 */ - "getClosureProcessor\0" /* 1802 */ - "frame\0" /* 1822 */ - "processDeferredByRefClosureBodies\0" /* 1828 */ - "statementStartTokenPositions\0" /* 1862 */ - "observeClosureSignatures\0" /* 1891 */ - "recordedEntry\0" /* 1916 */ - "recordedExit\0" /* 1930 */ - "withRecordedConstraints\0" /* 1943 */ - "containsLabels\0" /* 1967 */ - "from\0" /* 1982 */ - "to\0" /* 1987 */ - "appendRecordedStatementResults\0" /* 1990 */ - "PHPStan\\Analyser\\VariableFlow\0" /* 2021 */ - "getVariableMentionFlow\0" /* 2051 */ - "variableNames\0" /* 2074 */ - "statementMentionsAnyVariable\0" /* 2088 */ - "node\0" /* 2117 */ - "mentionsEverything\0" /* 2122 */ - "collectMentionedVariables\0" /* 2141 */ - "statement\0" /* 2167 */ - "getOverridingThrowPoints\0" /* 2177 */ - "defaultExpr\0" /* 2202 */ - "PhpParser\\Node\\Expr\0" /* 2214 */ - "processStmtVarAnnotation\0" /* 2234 */ - "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 2259 */ - "findSingleVariableLessVarTag\0" /* 2285 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 2314 */ - "emitVarTagChangedNode"; /* 2368 */ + "staticWalk\0" /* 1316 */ + "frame\0" /* 1327 */ + "parentConstraints\0" /* 1333 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1351 */ + "replayStaticVariableWalk\0" /* 1405 */ + "bodyScope\0" /* 1430 */ + "endState\0" /* 1440 */ + "moreScopes\0" /* 1449 */ + "collectStaticVariableScopes\0" /* 1460 */ + "collectStaticVariableStateScopes\0" /* 1488 */ + "names\0" /* 1521 */ + "scopes\0" /* 1527 */ + "collectStaticVariableTypes\0" /* 1534 */ + "runs\0" /* 1561 */ + "types\0" /* 1566 */ + "walkedConditionalExpressions\0" /* 1572 */ + "enteredTypes\0" /* 1601 */ + "collectStaticVariableConditionalExpressions\0" /* 1614 */ + "isWalkedStaticVariableState\0" /* 1658 */ + "a\0" /* 1686 */ + "b\0" /* 1688 */ + "equalStaticVariableStates\0" /* 1690 */ + "equalStaticVariableConditionalExpressions\0" /* 1716 */ + "PHPStan\\Analyser\\ConditionalExpressionHolder\0" /* 1758 */ + "equalConditionalExpressionHolders\0" /* 1803 */ + "walkTypes\0" /* 1837 */ + "generalize\0" /* 1847 */ + "joinStaticVariableTypes\0" /* 1858 */ + "PHPStan\\Analyser\\ClosureProcessor\0" /* 1882 */ + "getClosureProcessor\0" /* 1916 */ + "processDeferredByRefClosureBodies\0" /* 1936 */ + "statementStartTokenPositions\0" /* 1970 */ + "observeClosureSignatures\0" /* 1999 */ + "recordedEntry\0" /* 2024 */ + "recordedExit\0" /* 2038 */ + "withRecordedConstraints\0" /* 2051 */ + "containsLabels\0" /* 2075 */ + "from\0" /* 2090 */ + "to\0" /* 2095 */ + "appendRecordedStatementResults\0" /* 2098 */ + "PHPStan\\Analyser\\VariableFlow\0" /* 2129 */ + "getVariableMentionFlow\0" /* 2159 */ + "variableNames\0" /* 2182 */ + "statementMentionsAnyVariable\0" /* 2196 */ + "node\0" /* 2225 */ + "mentionsEverything\0" /* 2230 */ + "collectMentionedVariables\0" /* 2249 */ + "statement\0" /* 2275 */ + "getOverridingThrowPoints\0" /* 2285 */ + "defaultExpr\0" /* 2310 */ + "PhpParser\\Node\\Expr\0" /* 2322 */ + "processStmtVarAnnotation\0" /* 2342 */ + "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 2367 */ + "findSingleVariableLessVarTag\0" /* 2393 */ + "emitVarTagChangedNode"; /* 2422 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 15), /* __construct $fileTypeMapper */ reg::packed(43, 0, 68), /* __construct $templateArgumentObserver */ @@ -229,49 +232,61 @@ inline constexpr reg::PackedArg args[] = { reg::packed(592, 0, 477), /* inferStaticVariableTypes $storage */ reg::packed(600, 0, 608), /* inferStaticVariableTypes $context */ reg::packed(531, MAY_BE_ARRAY), /* inferStaticVariableTypes return */ - reg::packed(1316, 0, 422), /* collectStaticVariableScopes $bodyScope */ + reg::packed(357, 0, 375), /* replayStaticVariableWalk $nodeScopeResolver */ + reg::packed(865, MAY_BE_ARRAY), /* replayStaticVariableWalk $stmts */ + reg::packed(1234, MAY_BE_ARRAY), /* replayStaticVariableWalk $entries */ + reg::packed(1242, 0, 1252), /* replayStaticVariableWalk $recording */ + reg::packed(1316, MAY_BE_ARRAY), /* replayStaticVariableWalk $staticWalk */ + reg::packed(1327, 0, 1186), /* replayStaticVariableWalk $frame */ + reg::packed(1174, MAY_BE_NULL, 1186), /* replayStaticVariableWalk $parentFrame */ + reg::packed(1333, MAY_BE_NULL, 1351), /* replayStaticVariableWalk $parentConstraints */ + reg::packed(592, 0, 477), /* replayStaticVariableWalk $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* replayStaticVariableWalk $nodeCallback */ + reg::packed(416, 0, 422), /* replayStaticVariableWalk $scope */ + reg::packed(531, 0, 871), /* replayStaticVariableWalk return */ + reg::packed(1430, 0, 422), /* collectStaticVariableScopes $bodyScope */ reg::packed(1242, 0, 1252), /* collectStaticVariableScopes $recording */ - reg::packed(1326, 0, 964), /* collectStaticVariableScopes $endState */ - reg::packed(1335, MAY_BE_ARRAY), /* collectStaticVariableScopes $moreScopes */ + reg::packed(1440, 0, 964), /* collectStaticVariableScopes $endState */ + reg::packed(1449, MAY_BE_ARRAY), /* collectStaticVariableScopes $moreScopes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableScopes return */ - reg::packed(1316, 0, 422), /* collectStaticVariableStateScopes $bodyScope */ + reg::packed(1430, 0, 422), /* collectStaticVariableStateScopes $bodyScope */ reg::packed(1242, 0, 1252), /* collectStaticVariableStateScopes $recording */ - reg::packed(1326, 0, 964), /* collectStaticVariableStateScopes $endState */ - reg::packed(1335, MAY_BE_ARRAY), /* collectStaticVariableStateScopes $moreScopes */ + reg::packed(1440, 0, 964), /* collectStaticVariableStateScopes $endState */ + reg::packed(1449, MAY_BE_ARRAY), /* collectStaticVariableStateScopes $moreScopes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableStateScopes return */ - reg::packed(1407, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ - reg::packed(1413, MAY_BE_ARRAY), /* collectStaticVariableTypes $scopes */ + reg::packed(1521, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ + reg::packed(1527, MAY_BE_ARRAY), /* collectStaticVariableTypes $scopes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableTypes return */ - reg::packed(1447, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $runs */ - reg::packed(1413, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $scopes */ + reg::packed(1561, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $runs */ + reg::packed(1527, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $scopes */ reg::packed(592, 0, 477), /* collectStaticVariableConditionalExpressions $storage */ - reg::packed(1452, MAY_BE_NULL | MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $types */ - reg::packed(1458, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $walkedConditionalExpressions */ - reg::packed(1487, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $enteredTypes */ + reg::packed(1566, MAY_BE_NULL | MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $types */ + reg::packed(1572, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $walkedConditionalExpressions */ + reg::packed(1601, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $enteredTypes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions return */ reg::packed(958, MAY_BE_ARRAY), /* isWalkedStaticVariableState $state */ - reg::packed(1452, MAY_BE_ARRAY), /* isWalkedStaticVariableState $types */ + reg::packed(1566, MAY_BE_ARRAY), /* isWalkedStaticVariableState $types */ reg::packed(416, 0, 422), /* isWalkedStaticVariableState $scope */ - reg::packed(1458, MAY_BE_ARRAY), /* isWalkedStaticVariableState $walkedConditionalExpressions */ + reg::packed(1572, MAY_BE_ARRAY), /* isWalkedStaticVariableState $walkedConditionalExpressions */ reg::packed(531, MAY_BE_BOOL), /* isWalkedStaticVariableState return */ - reg::packed(1572, MAY_BE_ARRAY), /* equalStaticVariableStates $a */ - reg::packed(1574, MAY_BE_ARRAY), /* equalStaticVariableStates $b */ + reg::packed(1686, MAY_BE_ARRAY), /* equalStaticVariableStates $a */ + reg::packed(1688, MAY_BE_ARRAY), /* equalStaticVariableStates $b */ reg::packed(531, MAY_BE_BOOL), /* equalStaticVariableStates return */ - reg::packed(1572, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $a */ - reg::packed(1574, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $b */ + reg::packed(1686, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $a */ + reg::packed(1688, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $b */ reg::packed(531, MAY_BE_BOOL), /* equalStaticVariableConditionalExpressions return */ - reg::packed(1572, 0, 1644), /* equalConditionalExpressionHolders $a */ - reg::packed(1574, 0, 1644), /* equalConditionalExpressionHolders $b */ + reg::packed(1686, 0, 1758), /* equalConditionalExpressionHolders $a */ + reg::packed(1688, 0, 1758), /* equalConditionalExpressionHolders $b */ reg::packed(531, MAY_BE_BOOL), /* equalConditionalExpressionHolders return */ - reg::packed(1316, 0, 422), /* joinStaticVariableTypes $bodyScope */ - reg::packed(1407, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ - reg::packed(1452, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ - reg::packed(1723, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ - reg::packed(1733, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ + reg::packed(1430, 0, 422), /* joinStaticVariableTypes $bodyScope */ + reg::packed(1521, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ + reg::packed(1566, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ + reg::packed(1837, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ + reg::packed(1847, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ reg::packed(531, MAY_BE_ARRAY), /* joinStaticVariableTypes return */ - reg::packed(531, 0, 1768), /* getClosureProcessor return */ + reg::packed(531, 0, 1882), /* getClosureProcessor return */ reg::packed(357, 0, 375), /* processDeferredByRefClosureBodies $nodeScopeResolver */ - reg::packed(1822, 0, 1186), /* processDeferredByRefClosureBodies $frame */ + reg::packed(1327, 0, 1186), /* processDeferredByRefClosureBodies $frame */ reg::packed(958, 0, 964), /* processDeferredByRefClosureBodies $state */ reg::packed(592, 0, 477), /* processDeferredByRefClosureBodies $storage */ reg::packed(518, MAY_BE_CALLABLE), /* processDeferredByRefClosureBodies $nodeCallback */ @@ -279,51 +294,51 @@ inline constexpr reg::PackedArg args[] = { reg::packed(357, 0, 375), /* observeClosureSignatures $nodeScopeResolver */ reg::packed(556, 0, 567), /* observeClosureSignatures $parentNode */ reg::packed(865, MAY_BE_ARRAY), /* observeClosureSignatures $stmts */ - reg::packed(1822, 0, 1186), /* observeClosureSignatures $frame */ + reg::packed(1327, 0, 1186), /* observeClosureSignatures $frame */ reg::packed(1234, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ reg::packed(592, 0, 477), /* observeClosureSignatures $storage */ reg::packed(600, 0, 608), /* observeClosureSignatures $context */ - reg::packed(1862, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ + reg::packed(1970, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ reg::packed(531, 0, 1186), /* observeClosureSignatures return */ reg::packed(416, 0, 422), /* withRecordedConstraints $scope */ - reg::packed(1916, 0, 422), /* withRecordedConstraints $recordedEntry */ - reg::packed(1930, 0, 422), /* withRecordedConstraints $recordedExit */ + reg::packed(2024, 0, 422), /* withRecordedConstraints $recordedEntry */ + reg::packed(2038, 0, 422), /* withRecordedConstraints $recordedExit */ reg::packed(531, 0, 422), /* withRecordedConstraints return */ reg::packed(865, MAY_BE_ARRAY), /* containsLabels $stmts */ reg::packed(531, MAY_BE_BOOL), /* containsLabels return */ reg::packed(958, 0, 964), /* appendRecordedStatementResults $state */ - reg::packed(1982, 0, 964), /* appendRecordedStatementResults $from */ - reg::packed(1987, 0, 964), /* appendRecordedStatementResults $to */ + reg::packed(2090, 0, 964), /* appendRecordedStatementResults $from */ + reg::packed(2095, 0, 964), /* appendRecordedStatementResults $to */ reg::packed(531, MAY_BE_VOID), /* appendRecordedStatementResults return */ reg::packed(933, 0, 938), /* getVariableMentionFlow $stmt */ - reg::packed(531, MAY_BE_NULL, 2021), /* getVariableMentionFlow return */ + reg::packed(531, MAY_BE_NULL, 2129), /* getVariableMentionFlow return */ reg::packed(933, 0, 938), /* statementMentionsAnyVariable $stmt */ - reg::packed(2074, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ + reg::packed(2182, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ reg::packed(531, MAY_BE_BOOL), /* statementMentionsAnyVariable return */ - reg::packed(2117, 0, 567), /* collectMentionedVariables $node */ - reg::packed(1407, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ - reg::packed(2122, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ + reg::packed(2225, 0, 567), /* collectMentionedVariables $node */ + reg::packed(1521, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ + reg::packed(2230, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ reg::packed(531, MAY_BE_VOID), /* collectMentionedVariables return */ - reg::packed(2167, 0, 938), /* getOverridingThrowPoints $statement */ + reg::packed(2275, 0, 938), /* getOverridingThrowPoints $statement */ reg::packed(416, 0, 422), /* getOverridingThrowPoints $scope */ reg::packed(531, MAY_BE_NULL | MAY_BE_ARRAY), /* getOverridingThrowPoints return */ reg::packed(357, 0, 375), /* processStmtVarAnnotation $nodeScopeResolver */ reg::packed(416, 0, 422), /* processStmtVarAnnotation $scope */ reg::packed(592, 0, 477), /* processStmtVarAnnotation $storage */ reg::packed(933, 0, 938), /* processStmtVarAnnotation $stmt */ - reg::packed(2202, MAY_BE_NULL, 2214), /* processStmtVarAnnotation $defaultExpr */ + reg::packed(2310, MAY_BE_NULL, 2322), /* processStmtVarAnnotation $defaultExpr */ reg::packed(518, MAY_BE_CALLABLE), /* processStmtVarAnnotation $nodeCallback */ reg::packed(531, 0, 422), /* processStmtVarAnnotation return */ reg::packed(416, 0, 422), /* findSingleVariableLessVarTag $scope */ reg::packed(933, 0, 938), /* findSingleVariableLessVarTag $stmt */ - reg::packed(531, MAY_BE_NULL, 2259), /* findSingleVariableLessVarTag return */ + reg::packed(531, MAY_BE_NULL, 2367), /* findSingleVariableLessVarTag return */ reg::packed(357, 0, 375), /* emitVarTagChangedNode $nodeScopeResolver */ reg::packed(416, 0, 422), /* emitVarTagChangedNode $scope */ reg::packed(592, 0, 477), /* emitVarTagChangedNode $storage */ reg::packed(933, 0, 938), /* emitVarTagChangedNode $stmt */ - reg::packed(2202, 0, 2214), /* emitVarTagChangedNode $defaultExpr */ + reg::packed(2310, 0, 2322), /* emitVarTagChangedNode $defaultExpr */ reg::packed(518, MAY_BE_CALLABLE), /* emitVarTagChangedNode $nodeCallback */ - reg::packed(531, 0, 2314), /* emitVarTagChangedNode return */ + reg::packed(531, 0, 1351), /* emitVarTagChangedNode return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -341,28 +356,29 @@ inline constexpr sigtab::Sig processStatementStep = { { 1029 /* processStatement inline constexpr sigtab::Sig readEndStatementExprResult = { { 1084 /* readEndStatementExprResult */, 2, 54, 2, 56, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1111 /* processBodyStmtNodesTwoPass */, 7, 57, 7, 64, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig inferStaticVariableTypes = { { 1291 /* inferStaticVariableTypes */, 10, 65, 10, 75, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableScopes = { { 1346 /* collectStaticVariableScopes */, 4, 76, 4, 80, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableStateScopes = { { 1374 /* collectStaticVariableStateScopes */, 4, 81, 4, 85, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1420 /* collectStaticVariableTypes */, 2, 86, 2, 88, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableConditionalExpressions = { { 1500 /* collectStaticVariableConditionalExpressions */, 6, 89, 6, 95, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isWalkedStaticVariableState = { { 1544 /* isWalkedStaticVariableState */, 4, 96, 4, 100, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalStaticVariableStates = { { 1576 /* equalStaticVariableStates */, 2, 101, 2, 103, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalStaticVariableConditionalExpressions = { { 1602 /* equalStaticVariableConditionalExpressions */, 2, 104, 2, 106, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalConditionalExpressionHolders = { { 1689 /* equalConditionalExpressionHolders */, 2, 107, 2, 109, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig joinStaticVariableTypes = { { 1744 /* joinStaticVariableTypes */, 5, 110, 5, 115, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getClosureProcessor = { { 1802 /* getClosureProcessor */, 0, 116, 0, 116, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 1828 /* processDeferredByRefClosureBodies */, 5, 117, 5, 122, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig observeClosureSignatures = { { 1891 /* observeClosureSignatures */, 8, 123, 8, 131, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig withRecordedConstraints = { { 1943 /* withRecordedConstraints */, 3, 132, 3, 135, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig containsLabels = { { 1967 /* containsLabels */, 1, 136, 1, 137, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig appendRecordedStatementResults = { { 1990 /* appendRecordedStatementResults */, 3, 138, 3, 141, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getVariableMentionFlow = { { 2051 /* getVariableMentionFlow */, 1, 142, 1, 143, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 2088 /* statementMentionsAnyVariable */, 2, 144, 2, 146, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectMentionedVariables = { { 2141 /* collectMentionedVariables */, 3, 147, 3, 150, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getOverridingThrowPoints = { { 2177 /* getOverridingThrowPoints */, 2, 151, 2, 153, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmtVarAnnotation = { { 2234 /* processStmtVarAnnotation */, 6, 154, 6, 160, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 2285 /* findSingleVariableLessVarTag */, 2, 161, 2, 163, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2368 /* emitVarTagChangedNode */, 6, 164, 6, 170, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig replayStaticVariableWalk = { { 1405 /* replayStaticVariableWalk */, 11, 76, 11, 87, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableScopes = { { 1460 /* collectStaticVariableScopes */, 4, 88, 4, 92, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableStateScopes = { { 1488 /* collectStaticVariableStateScopes */, 4, 93, 4, 97, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1534 /* collectStaticVariableTypes */, 2, 98, 2, 100, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableConditionalExpressions = { { 1614 /* collectStaticVariableConditionalExpressions */, 6, 101, 6, 107, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isWalkedStaticVariableState = { { 1658 /* isWalkedStaticVariableState */, 4, 108, 4, 112, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalStaticVariableStates = { { 1690 /* equalStaticVariableStates */, 2, 113, 2, 115, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalStaticVariableConditionalExpressions = { { 1716 /* equalStaticVariableConditionalExpressions */, 2, 116, 2, 118, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalConditionalExpressionHolders = { { 1803 /* equalConditionalExpressionHolders */, 2, 119, 2, 121, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig joinStaticVariableTypes = { { 1858 /* joinStaticVariableTypes */, 5, 122, 5, 127, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClosureProcessor = { { 1916 /* getClosureProcessor */, 0, 128, 0, 128, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 1936 /* processDeferredByRefClosureBodies */, 5, 129, 5, 134, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig observeClosureSignatures = { { 1999 /* observeClosureSignatures */, 8, 135, 8, 143, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withRecordedConstraints = { { 2051 /* withRecordedConstraints */, 3, 144, 3, 147, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig containsLabels = { { 2075 /* containsLabels */, 1, 148, 1, 149, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig appendRecordedStatementResults = { { 2098 /* appendRecordedStatementResults */, 3, 150, 3, 153, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getVariableMentionFlow = { { 2159 /* getVariableMentionFlow */, 1, 154, 1, 155, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 2196 /* statementMentionsAnyVariable */, 2, 156, 2, 158, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectMentionedVariables = { { 2249 /* collectMentionedVariables */, 3, 159, 3, 162, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getOverridingThrowPoints = { { 2285 /* getOverridingThrowPoints */, 2, 163, 2, 165, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmtVarAnnotation = { { 2342 /* processStmtVarAnnotation */, 6, 166, 6, 172, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 2393 /* findSingleVariableLessVarTag */, 2, 173, 2, 175, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2422 /* emitVarTagChangedNode */, 6, 176, 6, 182, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StatementsHandler diff --git a/turbo-ext/src/generated/StaticVariableInference.h b/turbo-ext/src/generated/StaticVariableInference.h index 43b593ce60c..0ec6ce6026c 100644 --- a/turbo-ext/src/generated/StaticVariableInference.h +++ b/turbo-ext/src/generated/StaticVariableInference.h @@ -38,56 +38,65 @@ inline constexpr char strings[] = "stmts\0" /* 105 */ "\0" /* 111 */ "getSites\0" /* 112 */ - "scope\0" /* 121 */ - "PHPStan\\Analyser\\MutatingScope\0" /* 127 */ - "var\0" /* 158 */ - "PhpParser\\Node\\Expr\\Variable\0" /* 162 */ - "isInferred\0" /* 191 */ - "getResolvedTypes\0" /* 202 */ - "getRuns\0" /* 219 */ - "stmt\0" /* 227 */ - "PhpParser\\Node\\Stmt\\Static_\0" /* 232 */ - "getResolvedConditionalExpressions\0" /* 260 */ - "node\0" /* 294 */ - "canRunUserCode\0" /* 299 */ - "siteIds\0" /* 314 */ - "declaresOnlySites\0" /* 322 */ - "scanSites\0" /* 340 */ - "expr\0" /* 350 */ - "PhpParser\\Node\\Expr\0" /* 355 */ - "names\0" /* 375 */ - "collectRootNames\0" /* 381 */ - "hasVarTag"; /* 398 */ + "index\0" /* 121 */ + "hasFunctionLikeFrom\0" /* 127 */ + "scope\0" /* 147 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 153 */ + "var\0" /* 184 */ + "PhpParser\\Node\\Expr\\Variable\0" /* 188 */ + "isInferred\0" /* 217 */ + "getResolvedTypes\0" /* 228 */ + "getRuns\0" /* 245 */ + "stmt\0" /* 253 */ + "PhpParser\\Node\\Stmt\\Static_\0" /* 258 */ + "getResolvedConditionalExpressions\0" /* 286 */ + "node\0" /* 320 */ + "canRunUserCode\0" /* 325 */ + "siteIds\0" /* 340 */ + "declaresOnlySites\0" /* 348 */ + "scanSites\0" /* 366 */ + "findLastFunctionLikeStatement\0" /* 376 */ + "expr\0" /* 406 */ + "PhpParser\\Node\\Expr\0" /* 411 */ + "names\0" /* 431 */ + "collectRootNames\0" /* 437 */ + "hasVarTag"; /* 454 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 19), /* __construct $reflectionProvider */ reg::packed(57, MAY_BE_BOOL), /* __construct $enabled */ reg::packed(77, 0, 90), /* getSites $functionLike */ reg::packed(105, MAY_BE_ARRAY), /* getSites $stmts */ reg::packed(111, MAY_BE_ARRAY), /* getSites return */ - reg::packed(121, 0, 127), /* isInferred $scope */ - reg::packed(158, 0, 162), /* isInferred $var */ + reg::packed(77, 0, 90), /* hasFunctionLikeFrom $functionLike */ + reg::packed(105, MAY_BE_ARRAY), /* hasFunctionLikeFrom $stmts */ + reg::packed(121, MAY_BE_LONG), /* hasFunctionLikeFrom $index */ + reg::packed(111, MAY_BE_BOOL), /* hasFunctionLikeFrom return */ + reg::packed(147, 0, 153), /* isInferred $scope */ + reg::packed(184, 0, 188), /* isInferred $var */ reg::packed(111, MAY_BE_BOOL), /* isInferred return */ - reg::packed(121, 0, 127), /* getResolvedTypes $scope */ - reg::packed(158, 0, 162), /* getResolvedTypes $var */ + reg::packed(147, 0, 153), /* getResolvedTypes $scope */ + reg::packed(184, 0, 188), /* getResolvedTypes $var */ reg::packed(111, MAY_BE_NULL | MAY_BE_ARRAY), /* getResolvedTypes return */ reg::packed(77, 0, 90), /* getRuns $functionLike */ reg::packed(105, MAY_BE_ARRAY), /* getRuns $stmts */ reg::packed(111, MAY_BE_ARRAY), /* getRuns return */ - reg::packed(121, 0, 127), /* getResolvedConditionalExpressions $scope */ - reg::packed(227, 0, 232), /* getResolvedConditionalExpressions $stmt */ + reg::packed(147, 0, 153), /* getResolvedConditionalExpressions $scope */ + reg::packed(253, 0, 258), /* getResolvedConditionalExpressions $stmt */ reg::packed(111, MAY_BE_ARRAY), /* getResolvedConditionalExpressions return */ - reg::packed(294, 0, 90), /* canRunUserCode $node */ - reg::packed(121, 0, 127), /* canRunUserCode $scope */ + reg::packed(320, 0, 90), /* canRunUserCode $node */ + reg::packed(147, 0, 153), /* canRunUserCode $scope */ reg::packed(111, MAY_BE_BOOL), /* canRunUserCode return */ - reg::packed(227, 0, 232), /* declaresOnlySites $stmt */ - reg::packed(314, MAY_BE_ARRAY), /* declaresOnlySites $siteIds */ + reg::packed(253, 0, 258), /* declaresOnlySites $stmt */ + reg::packed(340, MAY_BE_ARRAY), /* declaresOnlySites $siteIds */ reg::packed(111, MAY_BE_BOOL), /* declaresOnlySites return */ reg::packed(105, MAY_BE_ARRAY), /* scanSites $stmts */ reg::packed(111, MAY_BE_ARRAY), /* scanSites return */ - reg::packed(350, 0, 355), /* collectRootNames $expr */ - reg::packed(375, MAY_BE_ARRAY, reg::NoString, true, false), /* collectRootNames $names */ + reg::packed(105, MAY_BE_ARRAY), /* findLastFunctionLikeStatement $stmts */ + reg::packed(111, MAY_BE_LONG), /* findLastFunctionLikeStatement return */ + reg::packed(406, 0, 411), /* collectRootNames $expr */ + reg::packed(431, MAY_BE_ARRAY, reg::NoString, true, false), /* collectRootNames $names */ reg::packed(111, MAY_BE_VOID), /* collectRootNames return */ - reg::packed(227, 0, 232), /* hasVarTag $stmt */ + reg::packed(253, 0, 258), /* hasVarTag $stmt */ reg::packed(111, MAY_BE_BOOL), /* hasVarTag return */ }; using Sig = reg::Sig; @@ -97,15 +106,17 @@ using Sig = reg::Sig; namespace sig { inline constexpr sigtab::Sig __construct = { { 65 /* __construct */, 2, 0, 2, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getSites = { { 112 /* getSites */, 2, 2, 2, 4, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInferred = { { 191 /* isInferred */, 2, 5, 2, 7, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getResolvedTypes = { { 202 /* getResolvedTypes */, 2, 8, 2, 10, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getRuns = { { 219 /* getRuns */, 2, 11, 2, 13, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getResolvedConditionalExpressions = { { 260 /* getResolvedConditionalExpressions */, 2, 14, 2, 16, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canRunUserCode = { { 299 /* canRunUserCode */, 2, 17, 2, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig declaresOnlySites = { { 322 /* declaresOnlySites */, 2, 20, 2, 22, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig scanSites = { { 340 /* scanSites */, 1, 23, 1, 24, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig collectRootNames = { { 381 /* collectRootNames */, 2, 25, 2, 27, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig hasVarTag = { { 398 /* hasVarTag */, 1, 28, 1, 29, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig hasFunctionLikeFrom = { { 127 /* hasFunctionLikeFrom */, 3, 5, 3, 8, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInferred = { { 217 /* isInferred */, 2, 9, 2, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getResolvedTypes = { { 228 /* getResolvedTypes */, 2, 12, 2, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getRuns = { { 245 /* getRuns */, 2, 15, 2, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getResolvedConditionalExpressions = { { 286 /* getResolvedConditionalExpressions */, 2, 18, 2, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canRunUserCode = { { 325 /* canRunUserCode */, 2, 21, 2, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig declaresOnlySites = { { 348 /* declaresOnlySites */, 2, 24, 2, 26, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig scanSites = { { 366 /* scanSites */, 1, 27, 1, 28, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig findLastFunctionLikeStatement = { { 376 /* findLastFunctionLikeStatement */, 1, 29, 1, 30, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectRootNames = { { 437 /* collectRootNames */, 2, 31, 2, 33, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig hasVarTag = { { 454 /* hasVarTag */, 1, 34, 1, 35, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::StaticVariableInference diff --git a/turbo-ext/src/generated/TemplateArgumentObserver.h b/turbo-ext/src/generated/TemplateArgumentObserver.h index fd44452cb68..03d1f36b851 100644 --- a/turbo-ext/src/generated/TemplateArgumentObserver.h +++ b/turbo-ext/src/generated/TemplateArgumentObserver.h @@ -63,11 +63,12 @@ inline constexpr char strings[] = "replaceInferableTemplates\0" /* 634 */ "isCallArgument\0" /* 660 */ "observeSend\0" /* 675 */ - "observeArgument\0" /* 687 */ - "observeLowerBound\0" /* 703 */ - "declaredArgument\0" /* 721 */ - "isUninformativeSendTarget\0" /* 738 */ - "hasOnlyInferableTemplates"; /* 764 */ + "isInertSendTarget\0" /* 687 */ + "observeArgument\0" /* 705 */ + "observeLowerBound\0" /* 721 */ + "declaredArgument\0" /* 739 */ + "isUninformativeSendTarget\0" /* 756 */ + "hasOnlyInferableTemplates"; /* 782 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 5), /* collectSites $type */ reg::packed(23, 0, 24), /* collectSites return */ @@ -123,6 +124,9 @@ inline constexpr reg::PackedArg args[] = { reg::packed(143, 0, 5), /* observeSend $actual */ reg::packed(660, MAY_BE_BOOL, reg::NoString, false, false, 113), /* observeSend $isCallArgument */ reg::packed(23, 0, 24), /* observeSend return */ + reg::packed(134, 0, 5), /* isInertSendTarget $declared */ + reg::packed(660, MAY_BE_BOOL), /* isInertSendTarget $isCallArgument */ + reg::packed(23, MAY_BE_BOOL), /* isInertSendTarget return */ reg::packed(386, 0, 24), /* observeArgument $constraints */ reg::packed(162, 0, 5), /* observeArgument $parameterType */ reg::packed(176, 0, 5), /* observeArgument $argumentType */ @@ -131,7 +135,7 @@ inline constexpr reg::PackedArg args[] = { reg::packed(162, 0, 5), /* observeLowerBound $parameterType */ reg::packed(176, 0, 5), /* observeLowerBound $argumentType */ reg::packed(23, 0, 24), /* observeLowerBound return */ - reg::packed(721, 0, 5), /* isUninformativeSendTarget $declaredArgument */ + reg::packed(739, 0, 5), /* isUninformativeSendTarget $declaredArgument */ reg::packed(23, MAY_BE_BOOL), /* isUninformativeSendTarget return */ reg::packed(0, 0, 5), /* hasOnlyInferableTemplates $type */ reg::packed(23, MAY_BE_BOOL), /* hasOnlyInferableTemplates return */ @@ -157,10 +161,11 @@ inline constexpr sigtab::Sig collectArgument = { { 514 /* collectArgument */, 2, inline constexpr sigtab::Sig collectCall = { { 612 /* collectCall */, 3, 39, 4, 43, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig replaceInferableTemplates = { { 634 /* replaceInferableTemplates */, 4, 44, 4, 48, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig observeSend = { { 675 /* observeSend */, 3, 49, 4, 53, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig observeArgument = { { 687 /* observeArgument */, 3, 54, 3, 57, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig observeLowerBound = { { 703 /* observeLowerBound */, 3, 58, 3, 61, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isUninformativeSendTarget = { { 738 /* isUninformativeSendTarget */, 1, 62, 1, 63, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig hasOnlyInferableTemplates = { { 764 /* hasOnlyInferableTemplates */, 1, 64, 1, 65, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isInertSendTarget = { { 687 /* isInertSendTarget */, 2, 54, 2, 56, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig observeArgument = { { 705 /* observeArgument */, 3, 57, 3, 60, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig observeLowerBound = { { 721 /* observeLowerBound */, 3, 61, 3, 64, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isUninformativeSendTarget = { { 756 /* isUninformativeSendTarget */, 1, 65, 1, 66, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig hasOnlyInferableTemplates = { { 782 /* hasOnlyInferableTemplates */, 1, 67, 1, 68, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::TemplateArgumentObserver diff --git a/turbo-ext/src/generated/TypeCombinator.h b/turbo-ext/src/generated/TypeCombinator.h index 056b917e82d..303b41cf6d5 100644 --- a/turbo-ext/src/generated/TypeCombinator.h +++ b/turbo-ext/src/generated/TypeCombinator.h @@ -55,20 +55,25 @@ inline constexpr char strings[] = "processArrayTypes\0" /* 472 */ "optimizeConstantArrays\0" /* 490 */ "countConstantArrayValueTypes\0" /* 513 */ - "constantArrays\0" /* 542 */ - "preserveTaggedUnions\0" /* 557 */ - "reduceArrays\0" /* 578 */ - "PHPStan\\Type\\UnionType\0" /* 591 */ - "intersectFiniteUnions\0" /* 614 */ - "finiteUnionMembers\0" /* 636 */ - "intersect\0" /* 655 */ - "doIntersect\0" /* 665 */ - "PHPStan\\Type\\Constant\\ConstantArrayType\0" /* 677 */ - "intersectDefiniteConstantArrays\0" /* 717 */ - "PHPStan\\Type\\IntersectionType\0" /* 749 */ - "mergeIntersectionsForUnion\0" /* 779 */ - "removeFalsey\0" /* 806 */ - "removeTruthy"; /* 819 */ + "array\0" /* 542 */ + "PHPStan\\Type\\Constant\\ConstantArrayType\0" /* 548 */ + "getConstantScalarValuesByKey\0" /* 588 */ + "oneUnrelatedKeyAllowed\0" /* 617 */ + "haveDisjointConstantScalarValues\0" /* 640 */ + "constantArrays\0" /* 673 */ + "preserveTaggedUnions\0" /* 688 */ + "reduceArrays\0" /* 709 */ + "isPlainOrBenevolentUnion\0" /* 722 */ + "PHPStan\\Type\\UnionType\0" /* 747 */ + "intersectFiniteUnions\0" /* 770 */ + "finiteUnionMembers\0" /* 792 */ + "intersect\0" /* 811 */ + "doIntersect\0" /* 821 */ + "intersectDefiniteConstantArrays\0" /* 833 */ + "PHPStan\\Type\\IntersectionType\0" /* 865 */ + "mergeIntersectionsForUnion\0" /* 895 */ + "removeFalsey\0" /* 922 */ + "removeTruthy"; /* 935 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_VOID), /* clearCache return */ reg::packed(12, 0, 17), /* addNull $type */ @@ -118,23 +123,31 @@ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_ARRAY), /* optimizeConstantArrays return */ reg::packed(126, MAY_BE_ARRAY), /* countConstantArrayValueTypes $types */ reg::packed(0, MAY_BE_LONG), /* countConstantArrayValueTypes return */ - reg::packed(542, MAY_BE_ARRAY), /* reduceArrays $constantArrays */ - reg::packed(557, MAY_BE_BOOL), /* reduceArrays $preserveTaggedUnions */ + reg::packed(542, 0, 548), /* getConstantScalarValuesByKey $array */ + reg::packed(0, MAY_BE_NULL | MAY_BE_ARRAY), /* getConstantScalarValuesByKey return */ + reg::packed(164, MAY_BE_NULL | MAY_BE_ARRAY), /* haveDisjointConstantScalarValues $a */ + reg::packed(166, MAY_BE_NULL | MAY_BE_ARRAY), /* haveDisjointConstantScalarValues $b */ + reg::packed(617, MAY_BE_BOOL), /* haveDisjointConstantScalarValues $oneUnrelatedKeyAllowed */ + reg::packed(0, MAY_BE_BOOL), /* haveDisjointConstantScalarValues return */ + reg::packed(673, MAY_BE_ARRAY), /* reduceArrays $constantArrays */ + reg::packed(688, MAY_BE_BOOL), /* reduceArrays $preserveTaggedUnions */ reg::packed(0, MAY_BE_ARRAY), /* reduceArrays return */ - reg::packed(164, 0, 591), /* intersectFiniteUnions $a */ - reg::packed(166, 0, 591), /* intersectFiniteUnions $b */ + reg::packed(12, 0, 17), /* isPlainOrBenevolentUnion $type */ + reg::packed(0, MAY_BE_BOOL), /* isPlainOrBenevolentUnion return */ + reg::packed(164, 0, 747), /* intersectFiniteUnions $a */ + reg::packed(166, 0, 747), /* intersectFiniteUnions $b */ reg::packed(0, MAY_BE_NULL, 17), /* intersectFiniteUnions return */ - reg::packed(132, 0, 591), /* finiteUnionMembers $union */ + reg::packed(132, 0, 747), /* finiteUnionMembers $union */ reg::packed(0, MAY_BE_NULL | MAY_BE_ARRAY), /* finiteUnionMembers return */ reg::packed(126, 0, 17, false, true), /* intersect $types */ reg::packed(0, 0, 17), /* intersect return */ reg::packed(126, 0, 17, false, true), /* doIntersect $types */ reg::packed(0, 0, 17), /* doIntersect return */ - reg::packed(164, 0, 677), /* intersectDefiniteConstantArrays $a */ - reg::packed(166, 0, 677), /* intersectDefiniteConstantArrays $b */ + reg::packed(164, 0, 548), /* intersectDefiniteConstantArrays $a */ + reg::packed(166, 0, 548), /* intersectDefiniteConstantArrays $b */ reg::packed(0, 0, 17), /* intersectDefiniteConstantArrays return */ - reg::packed(164, 0, 749), /* mergeIntersectionsForUnion $a */ - reg::packed(166, 0, 749), /* mergeIntersectionsForUnion $b */ + reg::packed(164, 0, 865), /* mergeIntersectionsForUnion $a */ + reg::packed(166, 0, 865), /* mergeIntersectionsForUnion $b */ reg::packed(0, MAY_BE_NULL, 17), /* mergeIntersectionsForUnion return */ reg::packed(12, 0, 17), /* removeFalsey $type */ reg::packed(0, 0, 17), /* removeFalsey return */ @@ -167,15 +180,18 @@ inline constexpr sigtab::Sig findEmptyArrayNextToOversizedArray = { { 437 /* fin inline constexpr sigtab::Sig processArrayTypes = { { 472 /* processArrayTypes */, 1, 42, 1, 43, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig optimizeConstantArrays = { { 490 /* optimizeConstantArrays */, 1, 44, 1, 45, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig countConstantArrayValueTypes = { { 513 /* countConstantArrayValueTypes */, 1, 46, 1, 47, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig reduceArrays = { { 578 /* reduceArrays */, 2, 48, 2, 50, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intersectFiniteUnions = { { 614 /* intersectFiniteUnions */, 2, 51, 2, 53, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig finiteUnionMembers = { { 636 /* finiteUnionMembers */, 1, 54, 1, 55, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intersect = { { 655 /* intersect */, 0, 56, 1, 57, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig doIntersect = { { 665 /* doIntersect */, 0, 58, 1, 59, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig intersectDefiniteConstantArrays = { { 717 /* intersectDefiniteConstantArrays */, 2, 60, 2, 62, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig mergeIntersectionsForUnion = { { 779 /* mergeIntersectionsForUnion */, 2, 63, 2, 65, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig removeFalsey = { { 806 /* removeFalsey */, 1, 66, 1, 67, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig removeTruthy = { { 819 /* removeTruthy */, 1, 68, 1, 69, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getConstantScalarValuesByKey = { { 588 /* getConstantScalarValuesByKey */, 1, 48, 1, 49, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig haveDisjointConstantScalarValues = { { 640 /* haveDisjointConstantScalarValues */, 3, 50, 3, 53, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig reduceArrays = { { 709 /* reduceArrays */, 2, 54, 2, 56, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isPlainOrBenevolentUnion = { { 722 /* isPlainOrBenevolentUnion */, 1, 57, 1, 58, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intersectFiniteUnions = { { 770 /* intersectFiniteUnions */, 2, 59, 2, 61, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig finiteUnionMembers = { { 792 /* finiteUnionMembers */, 1, 62, 1, 63, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intersect = { { 811 /* intersect */, 0, 64, 1, 65, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig doIntersect = { { 821 /* doIntersect */, 0, 66, 1, 67, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig intersectDefiniteConstantArrays = { { 833 /* intersectDefiniteConstantArrays */, 2, 68, 2, 70, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig mergeIntersectionsForUnion = { { 895 /* mergeIntersectionsForUnion */, 2, 71, 2, 73, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig removeFalsey = { { 922 /* removeFalsey */, 1, 74, 1, 75, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig removeTruthy = { { 935 /* removeTruthy */, 1, 76, 1, 77, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::TypeCombinator diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 832fa6c67b1..a9928e8412f 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -1556,6 +1556,10 @@ extern zend_class_entry *pt_ce_volatile_expression_helper; zv::Val pt_expression_result_storage_new(); zv::Val pt_expression_result_storage_find(zval *storage, zval *expr); zv::Val pt_expression_result_storage_duplicate(zval *storage); +/* ExpressionResultStorage::findByRefInvocationWalks() / storeByRefInvocationWalk() + * (false = pending exception) */ +zv::Val pt_expression_result_storage_find_by_ref_invocation_walks(zval *storage, zval *closure); +[[nodiscard]] bool pt_expression_result_storage_store_by_ref_invocation_walk(zval *storage, zval *closure, zval *walk); /* the shadowing ExpressionResultStorageStack (ExpressionResultStorageStack.cpp) * — $stack->getCurrent(): the native body for a native stack, the method for * anything else; UNDEF = pending exception */ @@ -2021,6 +2025,7 @@ zv::Val pt_template_argument_frame_get_static_variable_conditional_expressions(z extern zend_class_entry *pt_ce_static_variable_inference; zv::Val pt_static_variable_inference_get_sites(zval *inference, zval *functionLike, zval *stmts); [[nodiscard]] bool pt_static_variable_inference_is_inferred(zval *inference, zval *scope, zval *var, bool &out); +[[nodiscard]] bool pt_static_variable_inference_has_function_like_from(zval *inference, zval *functionLike, zval *stmts, zend_long index, bool &out); zv::Val pt_static_variable_inference_get_resolved_types(zval *inference, zval *scope, zval *var); zv::Val pt_static_variable_inference_get_runs(zval *inference, zval *functionLike, zval *stmts); zv::Val pt_static_variable_inference_get_resolved_conditional_expressions(zval *inference, zval *scope, zval *stmt); @@ -3128,6 +3133,7 @@ zv::Val pt_closure_signature_inference_get_arrow_function_outer_variables(zval * zv::Val pt_closure_signature_inference_collect_escapes(zval *type); zv::Val pt_closure_signature_inference_collect_invoked_callee(zval *calleeType); zv::Val pt_closure_signature_inference_collect_absorbed(zval *input, zval *result); +zv::Val pt_closure_signature_inference_collect_absorbed_into(zval *inputs, zval *result); zv::Val pt_closure_signature_inference_collect_absorbed_in_union(zval *types); [[nodiscard]] bool pt_closure_signature_inference_has_markers(zval *type, bool &out); /* the handlers' `$scope->addTemplateArgumentConstraints( diff --git a/turbo-ext/tests/signature-parity.php b/turbo-ext/tests/signature-parity.php index 0f10d7c642a..64ea91b8121 100644 --- a/turbo-ext/tests/signature-parity.php +++ b/turbo-ext/tests/signature-parity.php @@ -39,6 +39,7 @@ ], 'PHPStan\\Analyser\\ExpressionResultStorage' => [ '~^property (exprResults is not declared natively|(exprsById|resultsById) is declared natively but not in PHP)$~' => 'the result table is two id-keyed arrays instead of the twin\'s SplObjectStorage', + '~^property (byRefInvocationWalks is not declared natively|byRefInvocationWalksById is declared natively but not in PHP)$~' => 'the by-ref invocation walks are an id-keyed array of [closure, walks] instead of the twin\'s SplObjectStorage', ], ]; diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index cf60242c5f3..ee3479e68b4 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -378,6 +378,21 @@ function check(bool $cond, string $msg): void check($storage->findExpressionResult($exprC) === null, "ERS $label: mergeResults ignores the other's fallback chain"); check($storage->findExpressionResult($exprA) === $resultB, "ERS $label: mergeResults keeps existing entries"); + $closureA = new \PhpParser\Node\Expr\Closure(); + $closureB = new \PhpParser\Node\Expr\Closure(); + $walkA = [null, true, false, 'entryA', 'exitA', []]; + $walkB = [null, false, false, 'entryB', 'exitB', []]; + $walkC = [null, false, true, 'entryC', 'exitC', []]; + $walks = new $storageClass(); + check($walks->findByRefInvocationWalks($closureA) === [], "ERS $label: no by-ref walks stored"); + $walks->storeByRefInvocationWalk($closureA, $walkA); + $walks->storeByRefInvocationWalk($closureA, $walkB); + check($walks->findByRefInvocationWalks($closureA) === [$walkA, $walkB], "ERS $label: by-ref walks in the order stored"); + check($walks->findByRefInvocationWalks($closureB) === [], "ERS $label: by-ref walks are per closure"); + $walksChild = $walks->duplicate(); + $walksChild->storeByRefInvocationWalk($closureA, $walkC); + check($walksChild->findByRefInvocationWalks($closureA) === [$walkC, $walkA, $walkB], "ERS $label: own by-ref walks before the fallback chain's"); + check($walks->findByRefInvocationWalks($closureA) === [$walkA, $walkB], "ERS $label: by-ref walks stored on a duplicate do not leak back"); }