diff --git a/src/Analyser/ExprHandler/Helper/ForeachEmptyOrObjectNarrowingHelper.php b/src/Analyser/ExprHandler/Helper/ForeachEmptyOrObjectNarrowingHelper.php new file mode 100644 index 00000000000..31aeb817482 --- /dev/null +++ b/src/Analyser/ExprHandler/Helper/ForeachEmptyOrObjectNarrowingHelper.php @@ -0,0 +1,156 @@ +composeScope($nodeScopeResolver, $scope, $condition, $identical, $isObjectCall, $iterateeResult, $storage) + ?? $nodeScopeResolver->narrowScopeWithCondition($scope, $condition, TypeSpecifierContext::createTruthy()); + } + + private function composeScope( + NodeScopeResolver $nodeScopeResolver, + MutatingScope $scope, + BooleanOr $condition, + Identical $identical, + FuncCall $isObjectCall, + ExpressionResult $iterateeResult, + ExpressionResultStorage $storage, + ): ?MutatingScope + { + $isObjectName = new FullyQualified('is_object'); + if (!$this->reflectionProvider->hasFunction($isObjectName, $scope)) { + return null; + } + $isObjectFunction = $this->reflectionProvider->getFunction($isObjectName, $scope); + $identicalNarrowingHelper = $this->identicalNarrowingHelper; + $defaultNarrowingHelper = $this->defaultNarrowingHelper; + $emptyArrayType = new ConstantArrayType([], []); + $subjectArgResult = $identicalNarrowingHelper->captureFirstArgResult($identical->left, $storage); + + $leftTypes = static fn (MutatingScope $s, TypeSpecifierContext $context): ?SpecifiedTypes => $identicalNarrowingHelper->specifyIdenticalAgainstType( + $identical->left, + $iterateeResult, + $identical->right, + $emptyArrayType, + $context, + $s, + $subjectArgResult, + static fn (): Type => self::emptyArrayComparison($iterateeResult->getType(), $emptyArrayType), + ); + $rightTypes = static function (MutatingScope $s, TypeSpecifierContext $context) use ($defaultNarrowingHelper, $isObjectCall, $isObjectFunction): ?SpecifiedTypes { + $acceptor = ParametersAcceptorSelector::selectFromArgs($s, $isObjectCall->getArgs(), $isObjectFunction->getVariants(), $isObjectFunction->getNamedArgumentsVariants()); + + return $defaultNarrowingHelper->specifyTypesFromConditionalReturnType($context, $isObjectCall, $acceptor, $s); + }; + + $leftTruthyTypes = $leftTypes($scope, TypeSpecifierContext::createTruthy()); + $leftFalseyTypes = $leftTypes($scope, TypeSpecifierContext::createFalsey()); + if ($leftTruthyTypes === null || $leftFalseyTypes === null) { + return null; + } + $leftTruthyScope = $scope->applySpecifiedTypes($leftTruthyTypes->setRootExpr($identical)); + $leftFalseyScope = $scope->applySpecifiedTypes($leftFalseyTypes->setRootExpr($identical)); + $rightTruthyTypes = $rightTypes($leftFalseyScope, TypeSpecifierContext::createTruthy()); + if ($rightTruthyTypes === null) { + return null; + } + $rightTruthyScope = $leftFalseyScope->applySpecifiedTypes($rightTruthyTypes->setRootExpr($isObjectCall)); + + $types = $this->booleanNarrowingHelper->specifyDisjunction( + $nodeScopeResolver, + $scope, + TypeSpecifierContext::createTruthy(), + $condition, + $identical, + static fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $leftTypes($s, $context) ?? new SpecifiedTypes(), + static fn (bool $nativeTypesPromoted): Type => self::emptyArrayComparison($nativeTypesPromoted ? $iterateeResult->getNativeType() : $iterateeResult->getType(), $emptyArrayType), + static fn (): MutatingScope => $leftTruthyScope, + static fn (): MutatingScope => $leftFalseyScope, + $isObjectCall, + static fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $rightTypes($s, $context) ?? new SpecifiedTypes(), + static fn (bool $nativeTypesPromoted): Type => self::isObjectVerdict($nativeTypesPromoted ? $iterateeResult->getNativeType() : $iterateeResult->getType()), + static fn (): MutatingScope => $rightTruthyScope, + ); + + return $scope->applySpecifiedTypes($types); + } + + /** The type of `$iteratee === []`. */ + private static function emptyArrayComparison(Type $iterateeType, ConstantArrayType $emptyArrayType): Type + { + if ($iterateeType->equals($emptyArrayType)) { + return new ConstantBooleanType(true); + } + if ($emptyArrayType->isSuperTypeOf($iterateeType)->no()) { + return new ConstantBooleanType(false); + } + + return new BooleanType(); + } + + /** The type of `is_object($iteratee)`. */ + private static function isObjectVerdict(Type $iterateeType): Type + { + $isObject = $iterateeType->isObject(); + if ($isObject->yes()) { + return new ConstantBooleanType(true); + } + if ($isObject->no()) { + return new ConstantBooleanType(false); + } + + return new BooleanType(); + } + +} diff --git a/src/Analyser/InternalStatementExitPoint.php b/src/Analyser/InternalStatementExitPoint.php index 568e4439b5d..9b7f979d78f 100644 --- a/src/Analyser/InternalStatementExitPoint.php +++ b/src/Analyser/InternalStatementExitPoint.php @@ -23,6 +23,11 @@ public function getStatement(): Stmt return $this->statement; } + public function withScope(MutatingScope $scope): self + { + return new self($this->statement, $scope); + } + public function getScope(): MutatingScope { return $this->scope; diff --git a/src/Analyser/InternalThrowPoint.php b/src/Analyser/InternalThrowPoint.php index 1f7a249c986..17d03489df3 100644 --- a/src/Analyser/InternalThrowPoint.php +++ b/src/Analyser/InternalThrowPoint.php @@ -56,6 +56,11 @@ public static function createFromPublic(ThrowPoint $throwPoint, MutatingScope $s return new self($scope, $throwPoint->getType(), $throwPoint->getNode(), $throwPoint->isExplicit(), $throwPoint->canContainAnyThrowable()); } + public function withScope(MutatingScope $scope): self + { + return new self($scope, $this->type, $this->node, $this->explicit, $this->canContainAnyThrowable); + } + public function getScope(): MutatingScope { return $this->scope; diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 80269413519..6d43f10e951 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -286,6 +286,70 @@ public function processStmtNodes( } } + /** + * The final walk of a loop body against its last convergence pass (see + * StatementsHandler::processLoopFinalStmtNodes()). + * + * @param Node\Stmt[] $stmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + * @param array{array, RecordingNodeCallback} $lastPass + */ + public function processLoopFinalStmtNodes( + Node $parentNode, + array $stmts, + MutatingScope $scope, + ExpressionResultStorage $storage, + callable $nodeCallback, + StatementContext $context, + array $lastPass, + ): InternalStatementResult + { + $pushStorage = $scope->getCurrentExpressionResultStorage() !== $storage; + if ($pushStorage) { + $scope->pushExpressionResultStorage($storage); + } + try { + return $this->statementsHandler->processLoopFinalStmtNodes($this, $parentNode, $stmts, $scope, $storage, $nodeCallback, $context, $lastPass); + } finally { + if ($pushStorage) { + $scope->popExpressionResultStorage(); + } + } + } + + /** + * One loop convergence pass over $stmts, re-walking only what differs from + * $previous, the previous pass (see StatementsHandler::processLoopPassStmtNodes()). + * + * @param Node\Stmt[] $stmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + * @param array{array, ?RecordingNodeCallback}|null $previous + * @param array{array, ?RecordingNodeCallback}|null $record + */ + public function processLoopPassStmtNodes( + Node $parentNode, + array $stmts, + MutatingScope $scope, + ExpressionResultStorage $storage, + callable $nodeCallback, + StatementContext $context, + ?array $previous, + ?array &$record, + ): InternalStatementResult + { + $pushStorage = $scope->getCurrentExpressionResultStorage() !== $storage; + if ($pushStorage) { + $scope->pushExpressionResultStorage($storage); + } + try { + return $this->statementsHandler->processLoopPassStmtNodes($this, $parentNode, $stmts, $scope, $storage, $nodeCallback, $context, $previous, $record); + } finally { + if ($pushStorage) { + $scope->popExpressionResultStorage(); + } + } + } + /** * @param Node\Stmt[] $stmts * @param callable(Node $node, Scope $scope): void $nodeCallback @@ -929,6 +993,8 @@ public function getAssignedVariables(Expr $expr): array private const REPLAYABLE_BODY_ATTRIBUTE = 'convergenceReplayableBody'; + private const CONTEXT_SENSITIVE_STATEMENT_ATTRIBUTE = 'convergenceContextSensitiveStatement'; + /** * Whether a recorded convergence pass over the loop body can replace the * final walk. A pass runs at deep statement context, the final walk at top @@ -958,6 +1024,20 @@ public function isReplayableConvergenceBody(Node $loopNode, array $bodyStmts): b return $replayable; } + /** A statement that walks differently in a convergence pass than in the final walk. */ + public function isContextSensitiveStatement(Node\Stmt $stmt): bool + { + $cached = $stmt->getAttribute(self::CONTEXT_SENSITIVE_STATEMENT_ATTRIBUTE); + if ($cached !== null) { + return $cached; + } + + $sensitive = $this->hasContextSensitiveConstruct($stmt); + $stmt->setAttribute(self::CONTEXT_SENSITIVE_STATEMENT_ATTRIBUTE, $sensitive); + + return $sensitive; + } + private function hasContextSensitiveConstruct(Node $node): bool { if ($node instanceof Expr\Closure) { diff --git a/src/Analyser/StatementsHandler.php b/src/Analyser/StatementsHandler.php index 2dc24588f17..aae13558898 100644 --- a/src/Analyser/StatementsHandler.php +++ b/src/Analyser/StatementsHandler.php @@ -31,6 +31,7 @@ use PHPStan\PhpDoc\Tag\VarTag; use PHPStan\TrinaryLogic; use PHPStan\Turbo\ShadowedByTurboExtension; +use PHPStan\Type\ClosureType; use PHPStan\Type\ErrorType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\MixedType; @@ -38,8 +39,10 @@ use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; use function array_column; use function array_fill_keys; +use function array_flip; use function array_key_exists; use function array_keys; use function array_merge; @@ -397,6 +400,216 @@ public function doProcessStmtNodes( return $statementResult; } + /** + * One loop convergence pass. Against the previous pass's per-statement + * entries, a statement is re-walked only when its entry state differs in a + * key spelling no variable, or it mentions a variable whose state differs; + * otherwise its recorded emissions are replayed and its recorded effect is + * carried onto the current scope. Once the entry state equals the previous + * pass's, the rest of the previous pass stands. $record receives this pass's + * entries for the next one. + * + * @param Node\Stmt[] $stmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + * @param array{array, ?RecordingNodeCallback}|null $previous + * @param array{array, ?RecordingNodeCallback}|null $record + * @param-out array{array, ?RecordingNodeCallback} $record + */ + public function processLoopPassStmtNodes( + NodeScopeResolver $nodeScopeResolver, + Node $parentNode, + array $stmts, + MutatingScope $scope, + ExpressionResultStorage $storage, + callable $nodeCallback, + StatementContext $context, + ?array $previous, + ?array &$record, + ): InternalStatementResult + { + $recording = $nodeCallback instanceof RecordingNodeCallback ? $nodeCallback : null; + [$result, $entries] = $this->walkAgainstPreviousPass($nodeScopeResolver, $parentNode, $stmts, $scope, $storage, $nodeCallback, $context, $previous, $recording, false); + $record = [$entries, $recording]; + + return $result; + } + + /** + * The final walk of a loop body that the last convergence pass could not + * stand in for as a whole: like a later pass against $lastPass, with the + * replayed emissions going to the real callback, and every statement + * holding a construct that walks differently outside a pass (a nested + * loop runs its own convergence only here) walked. + * + * @param Node\Stmt[] $stmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + * @param array{array, RecordingNodeCallback} $lastPass + */ + public function processLoopFinalStmtNodes( + NodeScopeResolver $nodeScopeResolver, + Node $parentNode, + array $stmts, + MutatingScope $scope, + ExpressionResultStorage $storage, + callable $nodeCallback, + StatementContext $context, + array $lastPass, + ): InternalStatementResult + { + return $this->walkAgainstPreviousPass($nodeScopeResolver, $parentNode, $stmts, $scope, $storage, $nodeCallback, $context, $lastPass, $nodeCallback, true)[0]; + } + + /** + * Against the previous pass's per-statement entries, a statement is + * re-walked only when its entry state differs in a key spelling no + * variable, or it mentions a variable whose state differs; otherwise its + * recorded emissions are replayed into $replayTarget (none without one) + * and its recorded effect is carried onto the current scope. Once the + * entry state equals the previous pass's, the rest of the previous pass + * stands. A final walk records no entries. + * + * @param Node\Stmt[] $stmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + * @param array{array, ?RecordingNodeCallback}|null $previous + * @param (callable(Node $node, Scope $scope): void)|null $replayTarget + * @return array{InternalStatementResult, array} + */ + private function walkAgainstPreviousPass( + NodeScopeResolver $nodeScopeResolver, + Node $parentNode, + array $stmts, + MutatingScope $scope, + ExpressionResultStorage $storage, + callable $nodeCallback, + StatementContext $context, + ?array $previous, + ?callable $replayTarget, + bool $finalWalk, + ): array + { + $recording = !$finalWalk && $nodeCallback instanceof RecordingNodeCallback ? $nodeCallback : null; + $previousRecording = $previous !== null ? $previous[1] : null; + // a replayed statement's emissions come from the previous recording; + // labels make the statement order no straight line + $canReplay = $previous !== null + && ($replayTarget === null || $previousRecording !== null) + && !$this->containsLabels($stmts); + $stmtCount = count($stmts); + // the last statement a final walk walks whatever the state + $lastWalked = -1; + if ($canReplay && $finalWalk) { + for ($i = $stmtCount - 1; $i >= 0; $i--) { + if ($nodeScopeResolver->isContextSensitiveStatement($stmts[$i])) { + $lastWalked = $i; + break; + } + } + } + $state = new StatementListWalkState($scope); + $entries = []; + for ($i = 0; $i < $stmtCount; $i++) { + if (!$finalWalk) { + $entries[$i] = [clone $state, $recording !== null ? $recording->count() : 0]; + } + if ($canReplay && ($i > $lastWalked || !$nodeScopeResolver->isContextSensitiveStatement($stmts[$i]))) { + [$recordedEntry, $offset] = $previous[0][$i]; + [$recordedExit, $nextOffset] = $previous[0][$i + 1]; + $differingRoots = $state->alreadyTerminated === $recordedEntry->alreadyTerminated + ? $state->scope->getDifferingVariableRoots($recordedEntry->scope) + : null; + if ($differingRoots === [] && $i > $lastWalked) { + // converged with the previous pass: the rest of it stands + [$recordedEnd, $endOffset] = $previous[0][$stmtCount]; + if ($replayTarget !== null && $previousRecording !== null) { + $nodeScopeResolver->replayRecordingRange($previousRecording, $offset, $endOffset, $replayTarget, $storage, $scope); + } + $this->appendRecordedStatementResults($state, $recordedEntry, $recordedEnd); + $state->scope = $recordedEnd->scope; + if (!$finalWalk) { + $base = $entries[$i][1]; + for ($j = $i; $j <= $stmtCount; $j++) { + $entries[$j] = [$previous[0][$j][0], $base + $previous[0][$j][1] - $offset]; + } + } + + return [$state->toResult(), $entries]; + } + if ($differingRoots !== null && !$this->statementMentionsAnyVariable($stmts[$i], $this->withByRefClosureHolders($state->scope, $differingRoots))) { + if ($replayTarget !== null && $previousRecording !== null) { + $nodeScopeResolver->replayRecordingRange($previousRecording, $offset, $nextOffset, $replayTarget, $storage, $scope); + } + $exitPointCount = count($state->exitPoints); + $throwPointCount = count($state->throwPoints); + $this->appendRecordedStatementResults($state, $recordedEntry, $recordedExit); + $this->rebaseAppendedPoints($state, $exitPointCount, $throwPointCount, $recordedEntry->scope); + $state->scope = $state->scope->withRecordedStatementDelta($recordedEntry->scope, $recordedExit->scope); + continue; + } + } + $this->processStatementStep($nodeScopeResolver, $parentNode, $stmts, $i, $stmts[$i], $state, $storage, $nodeCallback, $context, false); + } + if (!$finalWalk) { + $entries[$stmtCount] = [clone $state, $recording !== null ? $recording->count() : 0]; + } + + return [$state->toResult(), $entries]; + } + + /** + * $variableNames and the variables holding a closure that captures one of + * them by reference: invoking it walks its body with the variable's current + * state (ClosureSignatureInference), so `$c();` reads `$x` without spelling it. + * + * @param list $variableNames + * @return list + */ + private function withByRefClosureHolders(MutatingScope $scope, array $variableNames): array + { + if ($variableNames === []) { + return $variableNames; + } + $differing = array_flip($variableNames); + foreach ([...$scope->getDefinedVariables(), ...$scope->getMaybeDefinedVariables()] as $holder) { + if (isset($differing[$holder])) { + continue; + } + $type = $scope->getVariableType($holder); + foreach ($type instanceof UnionType ? $type->getTypes() : [$type] as $member) { + if (!$member instanceof ClosureType) { + continue; + } + foreach (array_keys($member->getByRefUseTypes()) as $captured) { + if (!isset($differing[$captured])) { + continue; + } + + $variableNames[] = $holder; + continue 3; + } + } + } + + return $variableNames; + } + + /** + * The exit and throw points a replayed statement appended carry scopes of + * the recorded walk; carry the statement's recorded effect onto the current + * entry scope instead. + */ + private function rebaseAppendedPoints(StatementListWalkState $state, int $exitPointCount, int $throwPointCount, MutatingScope $recordedEntry): void + { + $entry = $state->scope; + for ($i = $exitPointCount; $i < count($state->exitPoints); $i++) { + $point = $state->exitPoints[$i]; + $state->exitPoints[$i] = $point->withScope($entry->withRecordedStatementDelta($recordedEntry, $point->getScope())); + } + for ($i = $throwPointCount; $i < count($state->throwPoints); $i++) { + $point = $state->throwPoints[$i]; + $state->throwPoints[$i] = $point->withScope($entry->withRecordedStatementDelta($recordedEntry, $point->getScope())); + } + } + /** * One statement of a statement list, advancing $state past it. * diff --git a/src/Analyser/StmtHandler/DoWhileHandler.php b/src/Analyser/StmtHandler/DoWhileHandler.php index 31389cb599f..f05e8967912 100644 --- a/src/Analyser/StmtHandler/DoWhileHandler.php +++ b/src/Analyser/StmtHandler/DoWhileHandler.php @@ -65,8 +65,12 @@ public function processStmt( $replayPassStorage = null; $replayPassResult = null; $prevEntryScope = null; + $previousPass = null; + $previousPassStorage = null; if ($context->isTopLevel()) { $bodyIsReplayable = $nodeScopeResolver->isReplayableConvergenceBody($stmt, $stmt->stmts); + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) do { $prevScope = $bodyScope; $bodyScope = $bodyScope->mergeWith($scope); @@ -79,10 +83,17 @@ public function processStmt( } $prevEntryScope = $bodyScope; $storage = $originalStorage->duplicate(); - $bodyRecording = $bodyIsReplayable ? new RecordingNodeCallback() : new NoopNodeCallback(); + if ($previousPassStorage !== null) { + // the statements this pass replays answer from the results the previous pass stored + $storage->mergeResults($previousPassStorage); + } + $bodyRecording = new RecordingNodeCallback(); $scope->pushExpressionResultStorage($storage); try { - $bodyScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $bodyRecording, $context->enterDeep()->withoutTemplateArgumentResolution())->filterOutLoopExitPoints(); + $passRecord = null; + $bodyScopeResult = $nodeScopeResolver->processLoopPassStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, $bodyRecording, $context->enterDeep()->withoutTemplateArgumentResolution(), $previousPass, $passRecord)->filterOutLoopExitPoints(); + $previousPass = $passRecord; + $previousPassStorage = $storage; $backEdgeScope = $bodyScopeResult->getLoopBackEdgeScope(); $finalScope = $backEdgeScope === null ? $finalScope : $backEdgeScope->mergeWith($finalScope); foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { @@ -90,7 +101,7 @@ public function processStmt( } // the candidate to replace the final body walk when this pass's // entry turns out to be the fixpoint - if ($bodyRecording instanceof RecordingNodeCallback) { + if ($bodyIsReplayable) { $replayBodyRecording = $bodyRecording; $replayPassStorage = $storage; $replayPassResult = $bodyScopeResult; @@ -132,6 +143,11 @@ public function processStmt( $originalStorage->mergeResults($replayPassStorage); $nodeScopeResolver->replayRecording($replayBodyRecording, $nodeCallback, $originalStorage, $scope); $bodyScopeResult = $replayPassResult; + } elseif ($previousPass !== null && $previousPass[1] !== null) { + // the last pass stands in for the statements whose state it walked + // with - they replay through the real callback + $originalStorage->mergeResults($previousPassStorage); + $bodyScopeResult = $nodeScopeResolver->processLoopFinalStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context, $previousPass)->filterOutLoopExitPoints(); } else { $bodyScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); } diff --git a/src/Analyser/StmtHandler/ForHandler.php b/src/Analyser/StmtHandler/ForHandler.php index e59ec2e5d15..7a95e2c5710 100644 --- a/src/Analyser/StmtHandler/ForHandler.php +++ b/src/Analyser/StmtHandler/ForHandler.php @@ -198,6 +198,10 @@ public function processStmt( if ($context->isTopLevel()) { $count = 0; $prevEntryScope = null; + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) + $previousPass = null; + $previousPassStorage = null; do { $prevScope = $bodyScope; $storage = $originalStorage->duplicate(); @@ -209,6 +213,10 @@ public function processStmt( break; } $prevEntryScope = $bodyScope; + if ($previousPassStorage !== null) { + // the statements this pass replays answer from the results the previous pass stored + $storage->mergeResults($previousPassStorage); + } $scope->pushExpressionResultStorage($storage); try { $passFlows = []; @@ -217,7 +225,10 @@ public function processStmt( $bodyScope = $passCondResult->getTruthyScope(); $passFlows[] = $passCondResult->getVariableFlow(); } - $bodyScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep()->withoutTemplateArgumentResolution())->filterOutLoopExitPoints(); + $passRecord = null; + $bodyScopeResult = $nodeScopeResolver->processLoopPassStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep()->withoutTemplateArgumentResolution(), $previousPass, $passRecord)->filterOutLoopExitPoints(); + $previousPass = $passRecord; + $previousPassStorage = $storage; $passFlows[] = $bodyScopeResult->getVariableFlow(); $backEdgeScope = $bodyScopeResult->getLoopBackEdgeScope(); if ($backEdgeScope === null) { diff --git a/src/Analyser/StmtHandler/ForeachHandler.php b/src/Analyser/StmtHandler/ForeachHandler.php index dea3ecfb40b..9bd2d818f92 100644 --- a/src/Analyser/StmtHandler/ForeachHandler.php +++ b/src/Analyser/StmtHandler/ForeachHandler.php @@ -4,13 +4,11 @@ use IteratorAggregate; use PhpParser\Node; -use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\BinaryOp; -use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\List_; use PhpParser\Node\Expr\Variable; @@ -24,6 +22,7 @@ use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExpressionTypeHolder; use PHPStan\Analyser\ExprHandler\AssignHandler; +use PHPStan\Analyser\ExprHandler\Helper\ForeachEmptyOrObjectNarrowingHelper; use PHPStan\Analyser\ExprHandler\Helper\IdenticalNarrowingHelper; use PHPStan\Analyser\InternalStatementResult; use PHPStan\Analyser\InternalThrowPoint; @@ -188,6 +187,8 @@ static function () use ($condResult, $emptyArrayType): Type { } $originalStorage = $storage; + $previousPass = null; + $previousPassStorage = null; $replayBodyRecording = null; $replayPassStorage = null; $replayPassResult = null; @@ -226,6 +227,8 @@ static function () use ($condResult, $emptyArrayType): Type { } $count = 0; $prevEntryScope = null; + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) $bodyIsReplayable = $nodeScopeResolver->isReplayableConvergenceBody($stmt, $stmt->stmts); do { $prevScope = $bodyScope; @@ -238,11 +241,18 @@ static function () use ($condResult, $emptyArrayType): Type { } $prevEntryScope = $bodyScope; $storage = $originalStorage->duplicate(); - $bodyRecording = $bodyIsReplayable ? new RecordingNodeCallback() : new NoopNodeCallback(); + if ($previousPassStorage !== null) { + // the statements this pass replays answer from the results the previous pass stored + $storage->mergeResults($previousPassStorage); + } + $bodyRecording = new RecordingNodeCallback(); $scope->pushExpressionResultStorage($storage); try { $bodyScope = $this->enterForeach($nodeScopeResolver, $bodyScope, $storage, $originalScope, $stmt, $foreachIterateeType, $foreachNativeIterateeType, $nodeCallback); - $bodyScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $bodyRecording, $context->enterDeep()->withoutTemplateArgumentResolution())->filterOutLoopExitPoints(); + $passRecord = null; + $bodyScopeResult = $nodeScopeResolver->processLoopPassStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, $bodyRecording, $context->enterDeep()->withoutTemplateArgumentResolution(), $previousPass, $passRecord)->filterOutLoopExitPoints(); + $previousPass = $passRecord; + $previousPassStorage = $storage; $backEdgeScope = $bodyScopeResult->getLoopBackEdgeScope(); if ($backEdgeScope === null) { $bodyScope = $prevScope; @@ -254,7 +264,7 @@ static function () use ($condResult, $emptyArrayType): Type { } // the candidate to replace the final walk when this pass's // entry turns out to be the fixpoint - if ($bodyRecording instanceof RecordingNodeCallback) { + if ($bodyIsReplayable) { $replayBodyRecording = $bodyRecording; $replayPassStorage = $storage; $replayPassResult = $bodyScopeResult; @@ -287,6 +297,11 @@ static function () use ($condResult, $emptyArrayType): Type { $originalStorage->mergeResults($replayPassStorage); $nodeScopeResolver->replayRecording($replayBodyRecording, $nodeCallback, $originalStorage, $scope); $finalScopeResult = $replayPassResult; + } elseif ($previousPass !== null && $previousPass[1] !== null) { + // the last pass stands in for the statements whose state it walked + // with - they replay through the real callback + $originalStorage->mergeResults($previousPassStorage); + $finalScopeResult = $nodeScopeResolver->processLoopFinalStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context, $previousPass)->filterOutLoopExitPoints(); } else { $finalPassContext = $unrolledTotalKeys !== null ? $context->enterUnrolledForeach($unrolledTotalKeys) : $context; $finalScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $finalPassContext)->filterOutLoopExitPoints(); @@ -457,15 +472,9 @@ static function () use ($condResult, $emptyArrayType): Type { $isIterableAtLeastOnce = $exprType->isIterableAtLeastOnce(); if ($isIterableAtLeastOnce->maybe() || $exprType->isIterable()->no()) { - $finalScope = $finalScope->mergeWith($nodeScopeResolver->narrowScopeWithCondition($scope, new BooleanOr( - new BinaryOp\Identical( - $stmt->expr, - new Array_([]), - ), - new FuncCall(new Name\FullyQualified('is_object'), [ - new Arg($stmt->expr), - ]), - ), TypeSpecifierContext::createTruthy())); + $finalScope = $finalScope->mergeWith( + $this->container->getByType(ForeachEmptyOrObjectNarrowingHelper::class)->narrowToEmptyOrObject($nodeScopeResolver, $scope, $stmt->expr, $condResult, $storage), + ); } elseif ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { $finalScope = $scope; } elseif (!$this->polluteScopeWithAlwaysIterableForeach) { diff --git a/src/Analyser/StmtHandler/WhileHandler.php b/src/Analyser/StmtHandler/WhileHandler.php index 02b00e0fa37..aa198450901 100644 --- a/src/Analyser/StmtHandler/WhileHandler.php +++ b/src/Analyser/StmtHandler/WhileHandler.php @@ -92,6 +92,8 @@ public function processStmt( $replayPassStorage = null; $replayPassResult = null; $prevEntryScope = null; + $previousPass = null; + $previousPassStorage = null; if ($context->isTopLevel()) { $count = 0; $bodyIsReplayable = $nodeScopeResolver->isReplayableConvergenceBody($stmt, $stmt->stmts); @@ -106,13 +108,20 @@ public function processStmt( } $prevEntryScope = $bodyScope; $storage = $originalStorage->duplicate(); + if ($previousPassStorage !== null) { + // the statements this pass replays answer from the results the previous pass stored + $storage->mergeResults($previousPassStorage); + } $condRecording = $bodyIsReplayable ? new RecordingNodeCallback() : new NoopNodeCallback(); - $bodyRecording = $bodyIsReplayable ? new RecordingNodeCallback() : new NoopNodeCallback(); + $bodyRecording = new RecordingNodeCallback(); $scope->pushExpressionResultStorage($storage); try { $passCondResult = $nodeScopeResolver->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $condRecording, ExpressionContext::createDeep(resolveTemplateArguments: false)); $bodyScope = $passCondResult->getTruthyScope(); - $bodyScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $bodyRecording, $context->enterDeep()->withoutTemplateArgumentResolution())->filterOutLoopExitPoints(); + $passRecord = null; + $bodyScopeResult = $nodeScopeResolver->processLoopPassStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, $bodyRecording, $context->enterDeep()->withoutTemplateArgumentResolution(), $previousPass, $passRecord)->filterOutLoopExitPoints(); + $previousPass = $passRecord; + $previousPassStorage = $storage; $backEdgeScope = $bodyScopeResult->getLoopBackEdgeScope(); } finally { $scope->popExpressionResultStorage(); @@ -166,12 +175,23 @@ public function processStmt( $bodyCondResult = $replayCondResult; $finalScopeResult = $replayPassResult; } else { + $lastPass = $previousPass !== null && $previousPass[1] !== null ? $previousPass : null; + if ($lastPass !== null) { + // before the condition's real walk stores its own results + $originalStorage->mergeResults($previousPassStorage); + } $bodyCondResult = $nodeScopeResolver->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep($context->shouldResolveTemplateArguments())); // the While_ callback is deferred from processStmtNode(): it fires after // the condition's real walk stored its result, with the entry scope $nodeScopeResolver->callNodeCallback($nodeCallback, $stmt, $scope, $storage); $bodyScope = $bodyCondResult->getTruthyScope(); - $finalScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); + if ($lastPass !== null) { + // the last pass stands in for the statements whose state it walked + // with - they replay through the real callback + $finalScopeResult = $nodeScopeResolver->processLoopFinalStmtNodes($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context, $lastPass)->filterOutLoopExitPoints(); + } else { + $finalScopeResult = $nodeScopeResolver->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); + } } $backEdgeScope = $finalScopeResult->getLoopBackEdgeScope(); $backEdgeDead = $backEdgeScope === null; diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 4141864d6fc..8786a69ced6 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -90,6 +90,16 @@ public function processNode(Node $node, Scope $scope): array return []; } + // on the very operand types the walk saw, the operation's own type is the + // answer - the walk already priced it + if ( + $leftType->equals($scope->getType($left)) + && $rightType->equals($scope->getType($right)) + && !$scope->getType($node) instanceof ErrorType + ) { + return []; + } + if ($node instanceof Node\Expr\AssignOp) { $newNode = clone $node; $newNode->setAttribute('phpstan_cache_printer', null); diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 22991812fa2..4f75bb12110 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = '1cb5faf'; + public const EXPECTED_EXTENSION_VERSION = '80f2bb1'; private static bool $active = false; diff --git a/turbo-ext/src/Abi.cpp b/turbo-ext/src/Abi.cpp index e49d77d0d60..2e3ce0d961e 100644 --- a/turbo-ext/src/Abi.cpp +++ b/turbo-ext/src/Abi.cpp @@ -119,6 +119,11 @@ static void abi_try_assign_ref_true(zval *zv) ZEND_TRY_ASSIGN_REF_TRUE(zv); } +static void abi_try_assign_ref_copy(zval *zv, zval *value) +{ + ZEND_TRY_ASSIGN_REF_COPY(zv, value); +} + /* the neutral ZPP numbering of abi.h, translated for this engine */ static const int abi_zpp_errors[] = { ZPP_ERROR_OK, @@ -364,6 +369,7 @@ void pt_abi_init() pt_abi.call_stack_size_error = abi_call_stack_size_error; pt_abi.try_assign_ref_str = abi_try_assign_ref_str; pt_abi.try_assign_ref_true = abi_try_assign_ref_true; + pt_abi.try_assign_ref_copy = abi_try_assign_ref_copy; pt_abi.wrong_parameter_error = abi_wrong_parameter_error; pt_abi.parse_arg_bool_slow = abi_parse_arg_bool_slow; pt_abi.parse_arg_double_slow = abi_parse_arg_double_slow; diff --git a/turbo-ext/src/DoWhileHandler.cpp b/turbo-ext/src/DoWhileHandler.cpp index 3a66b20a14b..8bd3377df21 100644 --- a/turbo-ext/src/DoWhileHandler.cpp +++ b/turbo-ext/src/DoWhileHandler.cpp @@ -35,6 +35,15 @@ pt_property_site pt_dwh_stmts_site; } // namespace +/* the last convergence pass when it recorded its emissions (its + * [entries, recording] record), NULL otherwise */ +static zval *lastRecordedPass(zv::Val &previousPass) +{ + if (!previousPass.ref().isArray()) return NULL; + zval *recording = zend_hash_index_find(Z_ARRVAL_P(previousPass.raw()), 1); + return recording != NULL && Z_TYPE_P(recording) == IS_OBJECT ? previousPass.raw() : NULL; +} + namespace phpstanturbo { /* Mirrors PHPStan\Analyser\StmtHandler\DoWhileHandler; UNDEF = pending @@ -70,11 +79,15 @@ class DoWhileHandler zv::Val replayBodyRecording, replayPassStorage, replayPassResult; zv::Val prevEntryScope; + zv::Val previousPass = zv::Val::null(); + zv::Val previousPassStorage = zv::Val::null(); if (isTopLevel) { zval *stmts = ptsh::readNodeProperty(pt_dwh_stmts_site, stmt, PT_LC("stmts")); if (UNEXPECTED(stmts == NULL)) return zv::Val(); bool bodyIsReplayable; if (UNEXPECTED(!pt_node_scope_resolver_is_replayable_convergence_body(nodeScopeResolver, stmt, stmts, bodyIsReplayable))) return zv::Val(); + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) do { zv::Val prevScope = zv::Val::copyOf(bodyScope.ref()); bodyScope = pt_mutating_scope_merge_with(Z_OBJ_P(bodyScope.raw()), scope); @@ -93,7 +106,9 @@ class DoWhileHandler prevEntryScope = zv::Val::copyOf(bodyScope.ref()); zv::Val storage = pt_expression_result_storage_duplicate(originalStorage); if (UNEXPECTED(storage.isUndef())) return zv::Val(); - zv::Val bodyRecording = ptlh::newPassNodeCallback(bodyIsReplayable); + // the statements this pass replays answer from the results the previous pass stored + if (!previousPassStorage.isNull() && UNEXPECTED(!pt_expression_result_storage_merge_results(storage.raw(), previousPassStorage.raw()))) return zv::Val(); + zv::Val bodyRecording = ptlh::newPassNodeCallback(true); if (UNEXPECTED(bodyRecording.isUndef())) return zv::Val(); if (UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(scope), storage.raw()))) return zv::Val(); zv::Val bodyScopeResult, passCondResult; @@ -106,8 +121,11 @@ class DoWhileHandler if (UNEXPECTED(deepContext.isUndef())) return; zv::Val passContext = pt_statement_context_without_template_argument_resolution(deepContext.raw()); if (UNEXPECTED(passContext.isUndef())) return; - zv::Val walked = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage.raw(), bodyRecording.raw(), passContext.raw()); + zv::Val passRecord = zv::Val::null(); + zv::Val walked = pt_node_scope_resolver_process_loop_pass_stmt_nodes(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage.raw(), bodyRecording.raw(), passContext.raw(), previousPass.raw(), passRecord); if (UNEXPECTED(walked.isUndef())) return; + previousPass = std::move(passRecord); + previousPassStorage = zv::Val::copyOf(storage.ref()); bodyScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); if (UNEXPECTED(bodyScopeResult.isUndef())) return; zv::Val backEdgeScope = pt_internal_statement_result_loop_back_edge_scope(bodyScopeResult.raw()); @@ -188,6 +206,17 @@ class DoWhileHandler if (UNEXPECTED(!pt_expression_result_storage_merge_results(originalStorage, replayPassStorage.raw()))) return zv::Val(); if (UNEXPECTED(!pt_node_scope_resolver_replay_recording(nodeScopeResolver, replayBodyRecording.raw(), nodeCallback, originalStorage, scope))) return zv::Val(); bodyScopeResult = std::move(replayPassResult); + } else if (lastRecordedPass(previousPass) != NULL && !previousPassStorage.isNull()) { + // the last pass stands in for the statements whose state it walked + // with - they replay through the real callback + if (UNEXPECTED(!pt_expression_result_storage_merge_results(originalStorage, previousPassStorage.raw()))) return zv::Val(); + zval *stmts = ptsh::readNodeProperty(pt_dwh_stmts_site, stmt, PT_LC("stmts")); + if (UNEXPECTED(stmts == NULL)) return zv::Val(); + zv::Val stmtsHold = zv::Val::copyOf(zv::Ref(stmts)); + zv::Val walked = pt_node_scope_resolver_process_loop_final_stmt_nodes(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage, nodeCallback, context, previousPass.raw()); + if (UNEXPECTED(walked.isUndef())) return zv::Val(); + bodyScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); + if (UNEXPECTED(bodyScopeResult.isUndef())) return zv::Val(); } else { zval *stmts = ptsh::readNodeProperty(pt_dwh_stmts_site, stmt, PT_LC("stmts")); if (UNEXPECTED(stmts == NULL)) return zv::Val(); diff --git a/turbo-ext/src/ForHandler.cpp b/turbo-ext/src/ForHandler.cpp index 0cd0f205138..f187def3240 100644 --- a/turbo-ext/src/ForHandler.cpp +++ b/turbo-ext/src/ForHandler.cpp @@ -215,6 +215,10 @@ class ForHandler if (isTopLevel) { zend_long count = 0; zv::Val prevEntryScope; + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) + zv::Val previousPass = zv::Val::null(); + zv::Val previousPassStorage = zv::Val::null(); do { zv::Val prevScope = zv::Val::copyOf(bodyScope.ref()); zv::Val storage = pt_expression_result_storage_duplicate(originalStorage); @@ -232,6 +236,8 @@ class ForHandler } } prevEntryScope = zv::Val::copyOf(bodyScope.ref()); + // the statements this pass replays answer from the results the previous pass stored + if (!previousPassStorage.isNull() && UNEXPECTED(!pt_expression_result_storage_merge_results(storage.raw(), previousPassStorage.raw()))) return zv::Val(); if (UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(scope), storage.raw()))) return zv::Val(); zv::Arr passFlows = zv::Arr::empty(); bool brokeOut = false; @@ -258,8 +264,11 @@ class ForHandler if (UNEXPECTED(deepContext.isUndef())) return; zv::Val passContext = pt_statement_context_without_template_argument_resolution(deepContext.raw()); if (UNEXPECTED(passContext.isUndef())) return; - zv::Val walked = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage.raw(), noop.raw(), passContext.raw()); + zv::Val passRecord = zv::Val::null(); + zv::Val walked = pt_node_scope_resolver_process_loop_pass_stmt_nodes(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage.raw(), noop.raw(), passContext.raw(), previousPass.raw(), passRecord); if (UNEXPECTED(walked.isUndef())) return; + previousPass = std::move(passRecord); + previousPassStorage = zv::Val::copyOf(storage.ref()); zv::Val bodyScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); if (UNEXPECTED(bodyScopeResult.isUndef())) return; { diff --git a/turbo-ext/src/ForeachEmptyOrObjectNarrowingHelper.cpp b/turbo-ext/src/ForeachEmptyOrObjectNarrowingHelper.cpp new file mode 100644 index 00000000000..90b79dc1c38 --- /dev/null +++ b/turbo-ext/src/ForeachEmptyOrObjectNarrowingHelper.cpp @@ -0,0 +1,426 @@ +/* + * PHPStanTurbo\ForeachEmptyOrObjectNarrowingHelper — native implementation of + * PHPStan\Analyser\ExprHandler\Helper\ForeachEmptyOrObjectNarrowingHelper. + * + * A DI service (#[AutowiredService]): the constructor keeps the twin's + * arginfo so Nette autowires it, the four collaborators live in the twin's + * property slots. narrowToEmptyOrObject() composes the truthy narrowing of + * `$iteratee === [] || is_object($iteratee)` through + * BooleanNarrowingHelper::specifyDisjunction() the way the twin does; the + * twin's closures are native closures over the same captures, and the + * collaborators are called through their direct entries. + */ + +#include "support.h" +#include "generated/ForeachEmptyOrObjectNarrowingHelper.h" + +namespace slots = ptdecl::ForeachEmptyOrObjectNarrowingHelper::slot; +namespace sigs = ptdecl::ForeachEmptyOrObjectNarrowingHelper::sig; +#include "zv.h" +#include "TypeTraits.h" +#include "TypeOps.h" +#include "Engine.h" +#include "AnalyserValues.h" + +zend_class_entry *pt_ce_foreach_empty_or_object_narrowing_helper = nullptr; + +/* the permanent interned string of the twin's literal (module startup) */ +static zend_string *pt_feoh_is_object = nullptr; + +namespace { + +/* new FullyQualified('is_object') */ +zv::Val newIsObjectName() +{ + zval name; + ZVAL_INTERNED_STR(&name, pt_feoh_is_object); + return pt_name_node_new(PT_CLASS_FULLY_QUALIFIED, &name); +} + +/* TypeSpecifierContext::createTruthy() / ::createFalsey() as a zval */ +bool contextZval(zval *out, bool truthy) +{ + zend_object *context = truthy ? pt_type_specifier_context_create_truthy() : pt_type_specifier_context_create_falsey(); + if (UNEXPECTED(context == NULL)) return false; + ZVAL_OBJ(out, context); + return true; +} + +/* self::emptyArrayComparison(): the type of `$iteratee === []` */ +zv::Val emptyArrayComparison(zval *iterateeType, zval *emptyArrayType) +{ + if (UNEXPECTED(Z_TYPE_P(iterateeType) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function equals() on %s", zend_zval_value_name(iterateeType)); + return zv::Val(); + } + zv::Val equals = pt_type_op(Z_OBJ_P(iterateeType), PT_OP_EQUALS, 1, emptyArrayType); + if (UNEXPECTED(equals.isUndef())) return zv::Val(); + zval out; + if (zend_is_true(equals.raw())) { + if (UNEXPECTED(!pt_constant_boolean_type_new(&out, true))) return zv::Val(); + return zv::Val::adopt(out); + } + zv::Val superType = pt_type_op(Z_OBJ_P(emptyArrayType), PT_OP_IS_SUPER_TYPE_OF, 1, iterateeType); + if (UNEXPECTED(superType.isUndef())) return zv::Val(); + if (UNEXPECTED(Z_TYPE_P(superType.raw()) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function no() on %s", zend_zval_value_name(superType.raw())); + return zv::Val(); + } + if (pt_result_value(Z_OBJ_P(superType.raw())) == PT_TRI_NO) { + if (UNEXPECTED(!pt_constant_boolean_type_new(&out, false))) return zv::Val(); + return zv::Val::adopt(out); + } + if (UNEXPECTED(!pt_boolean_type_new(&out))) return zv::Val(); + return zv::Val::adopt(out); +} + +/* self::isObjectVerdict(): the type of `is_object($iteratee)` */ +zv::Val isObjectVerdict(zval *iterateeType) +{ + if (UNEXPECTED(Z_TYPE_P(iterateeType) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function isObject() on %s", zend_zval_value_name(iterateeType)); + return zv::Val(); + } + zend_long isObject = pt_type_call_trinary(Z_OBJ_P(iterateeType), PT_LC("isobject"), 0, NULL); + if (UNEXPECTED(isObject < 0)) return zv::Val(); + zval out; + if (isObject == PT_TRI_YES) { + if (UNEXPECTED(!pt_constant_boolean_type_new(&out, true))) return zv::Val(); + } else if (isObject == PT_TRI_NO) { + if (UNEXPECTED(!pt_constant_boolean_type_new(&out, false))) return zv::Val(); + } else if (UNEXPECTED(!pt_boolean_type_new(&out))) { + return zv::Val(); + } + return zv::Val::adopt(out); +} + +/* $iterateeResult->getType() / ->getNativeType() */ +zv::Val iterateeTypeOf(zval *iterateeResult, bool nativeTypesPromoted) +{ + return nativeTypesPromoted ? pt_expression_result_get_native_type(iterateeResult) : pt_expression_result_get_type(iterateeResult); +} + +/* static fn (): Type => self::emptyArrayComparison($iterateeResult->getType(), $emptyArrayType) + * — captures: $iterateeResult, $emptyArrayType */ +void identicalTypeBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) +{ + (void) argc; + (void) argv; + zv::Val iterateeType = pt_expression_result_get_type(&captures[0]); + if (UNEXPECTED(iterateeType.isUndef())) return; + zv::Val type = emptyArrayComparison(iterateeType.raw(), &captures[1]); + if (UNEXPECTED(type.isUndef())) return; + type.intoReturnValue(return_value); +} + +/* $leftTypes($s, $context): $identicalNarrowingHelper->specifyIdenticalAgainstType(...) + * — captures: $identicalNarrowingHelper, $identical->left, $iterateeResult, + * $identical->right, $emptyArrayType, $subjectArgResult, the identical type + * callback; null when the comparison cannot be composed */ +zv::Val leftTypesOf(zval *captures, zval *s, zval *context) +{ + return pt_identical_narrowing_helper_specify_identical_against_type(&captures[0], &captures[1], &captures[2], &captures[3], &captures[4], context, s, &captures[5], &captures[6]); +} + +/* static fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes + * => $leftTypes($s, $context) ?? new SpecifiedTypes() */ +void leftTypesBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) +{ + if (UNEXPECTED(argc < 2)) { + zend_throw_error(NULL, "phpstan_turbo: the left narrowing callback expects a scope and a context"); + return; + } + zv::Val types = leftTypesOf(captures, &argv[0], &argv[1]); + if (UNEXPECTED(types.isUndef())) return; + if (types.isNull()) types = pt_specified_types_new(); + if (UNEXPECTED(types.isUndef())) return; + types.intoReturnValue(return_value); +} + +/* $rightTypes($s, $context): the conditional return type narrowing of + * is_object() — captures: $defaultNarrowingHelper, $isObjectCall, its args, + * $isObjectFunction; null when there is none */ +zv::Val rightTypesOf(zval *captures, zval *s, zval *context) +{ + zval *isObjectFunction = &captures[3]; + if (UNEXPECTED(Z_TYPE_P(isObjectFunction) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function getVariants() on %s", zend_zval_value_name(isObjectFunction)); + return zv::Val(); + } + zv::Val variants = pt_type_call(Z_OBJ_P(isObjectFunction), PT_LC("getvariants"), 0, NULL); + if (UNEXPECTED(variants.isUndef())) return zv::Val(); + zv::Val namedArgumentsVariants = pt_type_call(Z_OBJ_P(isObjectFunction), PT_LC("getnamedargumentsvariants"), 0, NULL); + if (UNEXPECTED(namedArgumentsVariants.isUndef())) return zv::Val(); + zv::Val acceptor = pt_parameters_acceptor_selector_select_from_args(s, &captures[2], variants.raw(), namedArgumentsVariants.raw()); + if (UNEXPECTED(acceptor.isUndef())) return zv::Val(); + return pt_default_narrowing_helper_specify_types_from_conditional_return_type(&captures[0], context, &captures[1], acceptor.raw(), s); +} + +/* static fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes + * => $rightTypes($s, $context) ?? new SpecifiedTypes() */ +void rightTypesBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) +{ + if (UNEXPECTED(argc < 2)) { + zend_throw_error(NULL, "phpstan_turbo: the right narrowing callback expects a scope and a context"); + return; + } + zv::Val types = rightTypesOf(captures, &argv[0], &argv[1]); + if (UNEXPECTED(types.isUndef())) return; + if (types.isNull()) types = pt_specified_types_new(); + if (UNEXPECTED(types.isUndef())) return; + types.intoReturnValue(return_value); +} + +/* static fn (bool $nativeTypesPromoted): Type => self::emptyArrayComparison(...) + * — captures: $iterateeResult, $emptyArrayType */ +void leftTypeBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) +{ + zv::Val iterateeType = iterateeTypeOf(&captures[0], argc > 0 && zend_is_true(&argv[0])); + if (UNEXPECTED(iterateeType.isUndef())) return; + zv::Val type = emptyArrayComparison(iterateeType.raw(), &captures[1]); + if (UNEXPECTED(type.isUndef())) return; + type.intoReturnValue(return_value); +} + +/* static fn (bool $nativeTypesPromoted): Type => self::isObjectVerdict(...) + * — captures: $iterateeResult */ +void rightTypeBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) +{ + zv::Val iterateeType = iterateeTypeOf(&captures[0], argc > 0 && zend_is_true(&argv[0])); + if (UNEXPECTED(iterateeType.isUndef())) return; + zv::Val type = isObjectVerdict(iterateeType.raw()); + if (UNEXPECTED(type.isUndef())) return; + type.intoReturnValue(return_value); +} + +/* static fn (): MutatingScope => $scope — captures: $scope */ +void scopeBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) +{ + (void) argc; + (void) argv; + ZVAL_COPY(return_value, &captures[0]); +} + +/* $types->setRootExpr($rootExpr) applied to $scope */ +zv::Val applyRooted(zval *scope, zval *types, zval *rootExpr) +{ + zv::Val rooted = pt_specified_types_set_root_expr(Z_OBJ_P(types), rootExpr); + if (UNEXPECTED(rooted.isUndef())) return zv::Val(); + return pt_mutating_scope_apply_specified_types(Z_OBJ_P(scope), rooted.raw()); +} + +} // namespace + +namespace phpstanturbo { + +/* Mirrors PHPStan\Analyser\ExprHandler\Helper\ForeachEmptyOrObjectNarrowingHelper. */ +class ForeachEmptyOrObjectNarrowingHelper +{ +public: + explicit ForeachEmptyOrObjectNarrowingHelper(zend_object *self) : self(self) {} + + /* __construct(private ReflectionProvider $reflectionProvider, private + * IdenticalNarrowingHelper $identicalNarrowingHelper, private + * DefaultNarrowingHelper $defaultNarrowingHelper, private + * BooleanNarrowingHelper $booleanNarrowingHelper) */ + void construct(zval *reflectionProvider, zval *identicalNarrowingHelper, zval *defaultNarrowingHelper, zval *booleanNarrowingHelper) const + { + pt_write_slot(self, slots::reflectionProvider, reflectionProvider); + pt_write_slot(self, slots::identicalNarrowingHelper, identicalNarrowingHelper); + pt_write_slot(self, slots::defaultNarrowingHelper, defaultNarrowingHelper); + pt_write_slot(self, slots::booleanNarrowingHelper, booleanNarrowingHelper); + } + + /* Mirrors narrowToEmptyOrObject(). UNDEF = pending exception */ + zv::Val narrowToEmptyOrObject(zval *nodeScopeResolver, zval *scope, zval *iteratee, zval *iterateeResult, zval *storage) const + { + zval emptyItems; + ZVAL_EMPTY_ARRAY(&emptyItems); + zv::Val emptyArray = pt_type_new(PT_CLASS_ARRAY_EXPR, 1, &emptyItems); + if (UNEXPECTED(emptyArray.isUndef())) return zv::Val(); + zv::Args identicalArgv{iteratee, emptyArray.raw()}; + zv::Val identical = pt_type_new(PT_CLASS_IDENTICAL_EXPR, 2, identicalArgv); + if (UNEXPECTED(identical.isUndef())) return zv::Val(); + zv::Val name = newIsObjectName(); + if (UNEXPECTED(name.isUndef())) return zv::Val(); + zv::Val arg = pt_type_new(PT_CLASS_ARG, 1, iteratee); + if (UNEXPECTED(arg.isUndef())) return zv::Val(); + zv::Arr args = zv::Arr::create(1); + args.push(std::move(arg)); + zv::Args callArgv{name.raw(), args.raw()}; + zv::Val isObjectCall = pt_type_new(PT_CLASS_FUNC_CALL, 2, callArgv); + if (UNEXPECTED(isObjectCall.isUndef())) return zv::Val(); + zv::Args orArgv{identical.raw(), isObjectCall.raw()}; + zv::Val condition = pt_type_new(PT_CLASS_BOOLEAN_OR_EXPR, 2, orArgv); + if (UNEXPECTED(condition.isUndef())) return zv::Val(); + + zv::Val composed = composeScope(nodeScopeResolver, scope, condition.raw(), identical.raw(), iteratee, emptyArray.raw(), isObjectCall.raw(), args.raw(), iterateeResult, storage); + if (UNEXPECTED(composed.isUndef())) return zv::Val(); + if (!composed.isNull()) return composed; + zval truthy; + if (UNEXPECTED(!contextZval(&truthy, true))) return zv::Val(); + return pt_node_scope_resolver_narrow_scope_with_condition(nodeScopeResolver, scope, condition.raw(), &truthy); + } + + /* Mirrors the private composeScope(); null when a side cannot be + * composed, UNDEF = pending exception. The nodes' operands come in + * directly ($identical->left / ->right, the call's args) instead of + * being read back */ + zv::Val composeScope(zval *nodeScopeResolver, zval *scope, zval *condition, zval *identical, zval *identicalLeft, zval *identicalRight, zval *isObjectCall, zval *isObjectArgs, zval *iterateeResult, zval *storage) const + { + zval *reflectionProvider = OBJ_PROP_NUM(self, slots::reflectionProvider); + zval *identicalNarrowingHelper = OBJ_PROP_NUM(self, slots::identicalNarrowingHelper); + zval *defaultNarrowingHelper = OBJ_PROP_NUM(self, slots::defaultNarrowingHelper); + zval *booleanNarrowingHelper = OBJ_PROP_NUM(self, slots::booleanNarrowingHelper); + if (UNEXPECTED(Z_TYPE_P(booleanNarrowingHelper) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function specifyDisjunction() on %s", zend_zval_value_name(booleanNarrowingHelper)); + return zv::Val(); + } + + zv::Val isObjectName = newIsObjectName(); + if (UNEXPECTED(isObjectName.isUndef())) return zv::Val(); + bool hasFunction; + if (UNEXPECTED(!pt_reflection_provider_has_function(reflectionProvider, isObjectName.raw(), scope, hasFunction))) return zv::Val(); + if (!hasFunction) return zv::Val::null(); + zv::Val isObjectFunction = pt_reflection_provider_get_function(reflectionProvider, isObjectName.raw(), scope); + if (UNEXPECTED(isObjectFunction.isUndef())) return zv::Val(); + zv::Val emptyArrayType; + { + zval keyTypes, valueTypes, out; + ZVAL_EMPTY_ARRAY(&keyTypes); + ZVAL_EMPTY_ARRAY(&valueTypes); + if (UNEXPECTED(!pt_constant_array_type_new(&out, &keyTypes, &valueTypes))) return zv::Val(); + emptyArrayType = zv::Val::adopt(out); + } + zv::Val subjectArgResult = pt_identical_narrowing_helper_capture_first_arg_result(identicalNarrowingHelper, identicalLeft, storage); + if (UNEXPECTED(subjectArgResult.isUndef())) return zv::Val(); + + zv::Val identicalTypeCallback = pt_native_closure(&identicalTypeBody, iterateeResult, emptyArrayType.raw()); + zval leftCaptures[7]; + ZVAL_COPY_VALUE(&leftCaptures[0], identicalNarrowingHelper); + ZVAL_COPY_VALUE(&leftCaptures[1], identicalLeft); + ZVAL_COPY_VALUE(&leftCaptures[2], iterateeResult); + ZVAL_COPY_VALUE(&leftCaptures[3], identicalRight); + ZVAL_COPY_VALUE(&leftCaptures[4], emptyArrayType.raw()); + ZVAL_COPY_VALUE(&leftCaptures[5], subjectArgResult.raw()); + ZVAL_COPY_VALUE(&leftCaptures[6], identicalTypeCallback.raw()); + zval rightCaptures[4]; + ZVAL_COPY_VALUE(&rightCaptures[0], defaultNarrowingHelper); + ZVAL_COPY_VALUE(&rightCaptures[1], isObjectCall); + ZVAL_COPY_VALUE(&rightCaptures[2], isObjectArgs); + ZVAL_COPY_VALUE(&rightCaptures[3], isObjectFunction.raw()); + + zval truthy, falsey; + if (UNEXPECTED(!contextZval(&truthy, true) || !contextZval(&falsey, false))) return zv::Val(); + zv::Val leftTruthyTypes = leftTypesOf(leftCaptures, scope, &truthy); + if (UNEXPECTED(leftTruthyTypes.isUndef())) return zv::Val(); + zv::Val leftFalseyTypes = leftTypesOf(leftCaptures, scope, &falsey); + if (UNEXPECTED(leftFalseyTypes.isUndef())) return zv::Val(); + if (leftTruthyTypes.isNull() || leftFalseyTypes.isNull()) return zv::Val::null(); + zv::Val leftTruthyScope = applyRooted(scope, leftTruthyTypes.raw(), identical); + if (UNEXPECTED(leftTruthyScope.isUndef())) return zv::Val(); + zv::Val leftFalseyScope = applyRooted(scope, leftFalseyTypes.raw(), identical); + if (UNEXPECTED(leftFalseyScope.isUndef())) return zv::Val(); + zv::Val rightTruthyTypes = rightTypesOf(rightCaptures, leftFalseyScope.raw(), &truthy); + if (UNEXPECTED(rightTruthyTypes.isUndef())) return zv::Val(); + if (rightTruthyTypes.isNull()) return zv::Val::null(); + zv::Val rightTruthyScope = applyRooted(leftFalseyScope.raw(), rightTruthyTypes.raw(), isObjectCall); + if (UNEXPECTED(rightTruthyScope.isUndef())) return zv::Val(); + + zv::Val leftTypesCallback = pt_native_closure_new(&leftTypesBody, 7, leftCaptures); + zv::Val leftTypeCallback = pt_native_closure(&leftTypeBody, iterateeResult, emptyArrayType.raw()); + zv::Val leftTruthyScopeCallback = pt_native_closure(&scopeBody, leftTruthyScope.raw()); + zv::Val leftFalseyScopeCallback = pt_native_closure(&scopeBody, leftFalseyScope.raw()); + zv::Val rightTypesCallback = pt_native_closure_new(&rightTypesBody, 4, rightCaptures); + zv::Val rightTypeCallback = pt_native_closure(&rightTypeBody, iterateeResult); + zv::Val rightTruthyScopeCallback = pt_native_closure(&scopeBody, rightTruthyScope.raw()); + zv::Val types = pt_boolean_narrowing_helper_specify_disjunction( + Z_OBJ_P(booleanNarrowingHelper), + nodeScopeResolver, + scope, + &truthy, + condition, + identical, + leftTypesCallback.raw(), + leftTypeCallback.raw(), + leftTruthyScopeCallback.raw(), + leftFalseyScopeCallback.raw(), + isObjectCall, + rightTypesCallback.raw(), + rightTypeCallback.raw(), + rightTruthyScopeCallback.raw() + ); + if (UNEXPECTED(types.isUndef())) return zv::Val(); + return pt_mutating_scope_apply_specified_types(Z_OBJ_P(scope), types.raw()); + } + +private: + zend_object *self; +}; + +} // namespace phpstanturbo + +using phpstanturbo::ForeachEmptyOrObjectNarrowingHelper; + +/* {{{ engine ABI glue: parameter parsing + registration */ + +#include "reg.h" + +PT_MINIT_REGISTRATION(pt_register_foreach_empty_or_object_narrowing_helper) +{ + pt_feoh_is_object = zend_string_init_interned(PT_LC("is_object"), 1); + + reg::Class cls("PHPStan\\Analyser\\ExprHandler\\Helper\\ForeachEmptyOrObjectNarrowingHelper"); + ptdecl::ForeachEmptyOrObjectNarrowingHelper::declareClass(cls); + ptdecl::ForeachEmptyOrObjectNarrowingHelper::declareProperties(cls); + + /* the real parameter class names: the DI container autowires the + * service by reflecting the constructor */ + cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *reflectionProvider, *identicalNarrowingHelper, *defaultNarrowingHelper, *booleanNarrowingHelper; + if (!zp::parse(execute_data, reflectionProvider, identicalNarrowingHelper, defaultNarrowingHelper, booleanNarrowingHelper)) RETURN_THROWS(); + ForeachEmptyOrObjectNarrowingHelper(Z_OBJ_P(ZEND_THIS)).construct(reflectionProvider, identicalNarrowingHelper, defaultNarrowingHelper, booleanNarrowingHelper); + }); + + cls.method(sigs::narrowToEmptyOrObject, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *nodeScopeResolver, *scope, *iteratee, *iterateeResult, *storage; + if (!zp::parse(execute_data, nodeScopeResolver, scope, iteratee, iterateeResult, storage)) RETURN_THROWS(); + PT_RETURN_VAL(ForeachEmptyOrObjectNarrowingHelper(Z_OBJ_P(ZEND_THIS)).narrowToEmptyOrObject(nodeScopeResolver, scope, iteratee, iterateeResult, storage)); + }); + + cls.method(sigs::composeScope, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *nodeScopeResolver, *scope, *condition, *identical, *isObjectCall, *iterateeResult, *storage; + ZEND_PARSE_PARAMETERS_START(7, 7) + Z_PARAM_OBJECT(nodeScopeResolver) + Z_PARAM_OBJECT(scope) + Z_PARAM_OBJECT(condition) + Z_PARAM_OBJECT(identical) + Z_PARAM_OBJECT(isObjectCall) + Z_PARAM_OBJECT(iterateeResult) + Z_PARAM_OBJECT(storage) + ZEND_PARSE_PARAMETERS_END(); + /* the private method's operands read back from the nodes, as the twin does */ + zval leftRv, rightRv, argsRv; + zval *left = zend_read_property(Z_OBJCE_P(identical), Z_OBJ_P(identical), PT_LC("left"), 0, &leftRv); + zval *right = zend_read_property(Z_OBJCE_P(identical), Z_OBJ_P(identical), PT_LC("right"), 0, &rightRv); + zval *args = zend_read_property(Z_OBJCE_P(isObjectCall), Z_OBJ_P(isObjectCall), PT_LC("args"), 0, &argsRv); + if (UNEXPECTED(EG(exception))) RETURN_THROWS(); + PT_RETURN_VAL(ForeachEmptyOrObjectNarrowingHelper(Z_OBJ_P(ZEND_THIS)).composeScope(nodeScopeResolver, scope, condition, identical, left, right, isObjectCall, args, iterateeResult, storage)); + }); + + cls.method(sigs::emptyArrayComparison, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *iterateeType, *emptyArrayType; + if (!zp::parse(execute_data, iterateeType, emptyArrayType)) RETURN_THROWS(); + PT_RETURN_VAL(emptyArrayComparison(iterateeType, emptyArrayType)); + }); + + cls.method(sigs::isObjectVerdict, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *iterateeType; + if (!zp::parse(execute_data, iterateeType)) RETURN_THROWS(); + PT_RETURN_VAL(isObjectVerdict(iterateeType)); + }); + + cls.shadow(&pt_ce_foreach_empty_or_object_narrowing_helper); +} + +/* }}} */ diff --git a/turbo-ext/src/ForeachHandler.cpp b/turbo-ext/src/ForeachHandler.cpp index 49ca2f9827f..d98a92c834a 100644 --- a/turbo-ext/src/ForeachHandler.cpp +++ b/turbo-ext/src/ForeachHandler.cpp @@ -66,10 +66,10 @@ pt_method_site pt_feh_get_doc_comment_site; /* the permanent interned strings of the twin's literals (module startup) */ zend_string *pt_feh_identical_narrowing_helper_class = nullptr; +zend_string *pt_feh_empty_or_object_narrowing_helper_class = nullptr; zend_string *pt_feh_traversable_class = nullptr; zend_string *pt_feh_iterator_aggregate_class = nullptr; zend_string *pt_feh_get_iterator = nullptr; -zend_string *pt_feh_is_object = nullptr; /* $node->getAttributes() / ->setAttributes($attributes) */ zv::Val getAttributes(zval *node) @@ -278,6 +278,15 @@ zv::Val unionOfList(zv::Arr &list) } // namespace +/* the last convergence pass when it recorded its emissions (its + * [entries, recording] record), NULL otherwise */ +static zval *lastRecordedPass(zv::Val &previousPass) +{ + if (!previousPass.ref().isArray()) return NULL; + zval *recording = zend_hash_index_find(Z_ARRVAL_P(previousPass.raw()), 1); + return recording != NULL && Z_TYPE_P(recording) == IS_OBJECT ? previousPass.raw() : NULL; +} + namespace phpstanturbo { /* Mirrors PHPStan\Analyser\StmtHandler\ForeachHandler; UNDEF = pending @@ -437,6 +446,8 @@ class ForeachHandler } zv::Val replayBodyRecording, replayPassStorage, replayPassResult, replayEntryScope; + zv::Val previousPass = zv::Val::null(); + zv::Val previousPassStorage = zv::Val::null(); zv::Val unrolledEndScope; bool unrolled = false; zend_long unrolledTotalKeys = 0; @@ -487,6 +498,8 @@ class ForeachHandler if (UNEXPECTED(EG(exception))) return zv::Val(); zend_long count = 0; zv::Val prevEntryScope; + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) bool bodyIsReplayable; if (UNEXPECTED(!pt_node_scope_resolver_is_replayable_convergence_body(nodeScopeResolver, stmt, parts.stmts.raw(), bodyIsReplayable))) return zv::Val(); do { @@ -506,7 +519,9 @@ class ForeachHandler prevEntryScope = zv::Val::copyOf(bodyScope.ref()); storage = pt_expression_result_storage_duplicate(originalStorage); if (UNEXPECTED(storage.isUndef())) return zv::Val(); - zv::Val bodyRecording = ptlh::newPassNodeCallback(bodyIsReplayable); + // the statements this pass replays answer from the results the previous pass stored + if (!previousPassStorage.isNull() && UNEXPECTED(!pt_expression_result_storage_merge_results(storage.raw(), previousPassStorage.raw()))) return zv::Val(); + zv::Val bodyRecording = ptlh::newPassNodeCallback(true); if (UNEXPECTED(bodyRecording.isUndef())) return zv::Val(); if (UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(scope.raw()), storage.raw()))) return zv::Val(); zv::Val bodyScopeResult; @@ -519,8 +534,11 @@ class ForeachHandler if (UNEXPECTED(deepContext.isUndef())) return; zv::Val passContext = pt_statement_context_without_template_argument_resolution(deepContext.raw()); if (UNEXPECTED(passContext.isUndef())) return; - zv::Val walked = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, stmt, parts.stmts.raw(), bodyScope.raw(), storage.raw(), bodyRecording.raw(), passContext.raw()); + zv::Val passRecord = zv::Val::null(); + zv::Val walked = pt_node_scope_resolver_process_loop_pass_stmt_nodes(nodeScopeResolver, stmt, parts.stmts.raw(), bodyScope.raw(), storage.raw(), bodyRecording.raw(), passContext.raw(), previousPass.raw(), passRecord); if (UNEXPECTED(walked.isUndef())) return; + previousPass = std::move(passRecord); + previousPassStorage = zv::Val::copyOf(storage.ref()); bodyScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); if (UNEXPECTED(bodyScopeResult.isUndef())) return; zv::Val backEdgeScope = pt_internal_statement_result_loop_back_edge_scope(bodyScopeResult.raw()); @@ -562,6 +580,7 @@ class ForeachHandler bodyScope = pt_mutating_scope_merge_with(Z_OBJ_P(bodyScope.raw()), nonEmptyIterateeScope.raw()); if (UNEXPECTED(bodyScope.isUndef())) return zv::Val(); zv::Val finalEntryScope = zv::Val::copyOf(bodyScope.ref()); + zval *lastPass = lastRecordedPass(previousPass); zval *storage = originalStorage; bodyScope = enterForeach(nodeScopeResolver, bodyScope.raw(), storage, originalScope.raw(), stmt, parts, foreachIterateeType.raw(), foreachNativeIterateeType.raw(), nodeCallback); if (UNEXPECTED(bodyScope.isUndef())) return zv::Val(); @@ -577,6 +596,17 @@ class ForeachHandler if (UNEXPECTED(!pt_expression_result_storage_merge_results(originalStorage, replayPassStorage.raw()))) return zv::Val(); if (UNEXPECTED(!pt_node_scope_resolver_replay_recording(nodeScopeResolver, replayBodyRecording.raw(), nodeCallback, originalStorage, scope.raw()))) return zv::Val(); finalScopeResult = std::move(replayPassResult); + } else if (lastPass != NULL && !previousPassStorage.isNull()) { + // the last pass stands in for the statements whose state it walked + // with - they replay through the real callback + if (UNEXPECTED(!pt_expression_result_storage_merge_results(originalStorage, previousPassStorage.raw()))) return zv::Val(); + zval *stmts = ptsh::readNodeProperty(pt_feh_stmts_site, stmt, PT_LC("stmts")); + if (UNEXPECTED(stmts == NULL)) return zv::Val(); + zv::Val stmtsHold = zv::Val::copyOf(zv::Ref(stmts)); + zv::Val walked = pt_node_scope_resolver_process_loop_final_stmt_nodes(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage, nodeCallback, context, lastPass); + if (UNEXPECTED(walked.isUndef())) return zv::Val(); + finalScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); + if (UNEXPECTED(finalScopeResult.isUndef())) return zv::Val(); } else { zv::Val finalPassContext; if (unrolled) { @@ -735,7 +765,7 @@ class ForeachHandler bool resultAlwaysTerminating; if (UNEXPECTED(!pt_internal_statement_result_is_always_terminating(result, resultAlwaysTerminating))) return zv::Val(); if (narrowToEmpty) { - zv::Val emptyScope = narrowToEmptyOrObject(nodeScopeResolver, scope.raw(), iteratee); + zv::Val emptyScope = narrowToEmptyOrObject(nodeScopeResolver, scope.raw(), iteratee, condResult.raw(), originalStorage); if (UNEXPECTED(emptyScope.isUndef())) return zv::Val(); zv::Val merged = pt_mutating_scope_merge_with(Z_OBJ_P(finalScope.raw()), emptyScope.raw()); if (UNEXPECTED(merged.isUndef())) return zv::Val(); @@ -1194,37 +1224,14 @@ class ForeachHandler return true; } - /* $nodeScopeResolver->narrowScopeWithCondition($scope, new BooleanOr(new - * Identical($iteratee, new Array_([])), new FuncCall(new - * Name\FullyQualified('is_object'), [new Arg($iteratee)])), - * TypeSpecifierContext::createTruthy()) — the nodes created in the - * twin's order */ - static zv::Val narrowToEmptyOrObject(zval *nodeScopeResolver, zval *scope, zval *iteratee) + /* $this->container->getByType(ForeachEmptyOrObjectNarrowingHelper::class) + * ->narrowToEmptyOrObject($nodeScopeResolver, $scope, $stmt->expr, $condResult, $storage) */ + zv::Val narrowToEmptyOrObject(zval *nodeScopeResolver, zval *scope, zval *iteratee, zval *iterateeResult, zval *storage) const { - zv::Val emptyArray = newEmptyArrayExpr(); - if (UNEXPECTED(emptyArray.isUndef())) return zv::Val(); - zv::Args identicalArgv{iteratee, emptyArray.raw()}; - zv::Val identical = pt_type_new(PT_CLASS_IDENTICAL_EXPR, 2, identicalArgv); - if (UNEXPECTED(identical.isUndef())) return zv::Val(); - zval isObject; - ZVAL_INTERNED_STR(&isObject, pt_feh_is_object); - zv::Val name = pt_name_node_new(PT_CLASS_FULLY_QUALIFIED, &isObject); - if (UNEXPECTED(name.isUndef())) return zv::Val(); - zv::Val arg = pt_type_new(PT_CLASS_ARG, 1, iteratee); - if (UNEXPECTED(arg.isUndef())) return zv::Val(); - zv::Arr args = zv::Arr::create(1); - args.push(std::move(arg)); - zv::Args callArgv{name.raw(), args.raw()}; - zv::Val call = pt_type_new(PT_CLASS_FUNC_CALL, 2, callArgv); - if (UNEXPECTED(call.isUndef())) return zv::Val(); - zv::Args orArgv{identical.raw(), call.raw()}; - zv::Val booleanOr = pt_type_new(PT_CLASS_BOOLEAN_OR_EXPR, 2, orArgv); - if (UNEXPECTED(booleanOr.isUndef())) return zv::Val(); - zend_object *truthy = pt_type_specifier_context_create_truthy(); - if (UNEXPECTED(truthy == NULL)) return zv::Val(); - zval truthyContext; - ZVAL_OBJ(&truthyContext, truthy); - return pt_node_scope_resolver_narrow_scope_with_condition(nodeScopeResolver, scope, booleanOr.raw(), &truthyContext); + zv::Val helper = ptlh::containerGetByType(OBJ_PROP_NUM(self, slots::container), pt_feh_empty_or_object_narrowing_helper_class); + if (UNEXPECTED(helper.isUndef())) return zv::Val(); + zv::Args argv{nodeScopeResolver, scope, iteratee, iterateeResult, storage}; + return pt_type_call(Z_OBJ_P(helper.raw()), PT_LC("narrowtoemptyorobject"), 5, argv); } /* Mirrors the private enterForeach(). */ @@ -1982,10 +1989,10 @@ using phpstanturbo::ForeachHandler; PT_MINIT_REGISTRATION(pt_register_foreach_handler) { pt_feh_identical_narrowing_helper_class = zend_string_init_interned(PT_LC("PHPStan\\Analyser\\ExprHandler\\Helper\\IdenticalNarrowingHelper"), 1); + pt_feh_empty_or_object_narrowing_helper_class = zend_string_init_interned(PT_LC("PHPStan\\Analyser\\ExprHandler\\Helper\\ForeachEmptyOrObjectNarrowingHelper"), 1); pt_feh_traversable_class = zend_string_init_interned(PT_LC("Traversable"), 1); pt_feh_iterator_aggregate_class = zend_string_init_interned(PT_LC("IteratorAggregate"), 1); pt_feh_get_iterator = zend_string_init_interned(PT_LC("getIterator"), 1); - pt_feh_is_object = zend_string_init_interned(PT_LC("is_object"), 1); reg::Class cls("PHPStan\\Analyser\\StmtHandler\\ForeachHandler"); ptdecl::ForeachHandler::declareClass(cls); diff --git a/turbo-ext/src/InternalStatementExitPoint.cpp b/turbo-ext/src/InternalStatementExitPoint.cpp index e34d2647cf2..93e219e20a1 100644 --- a/turbo-ext/src/InternalStatementExitPoint.cpp +++ b/turbo-ext/src/InternalStatementExitPoint.cpp @@ -52,6 +52,14 @@ class InternalStatementExitPoint return pt_statement_exit_point_new(statement, scope); } + /* Mirrors withScope(): new self($this->statement, $scope) */ + zv::Val withScope(zval *scope) const + { + zval *statement = pt_typed_slot(self, slots::statement, self->ce, "statement"); + if (UNEXPECTED(statement == NULL)) return zv::Val(); + return create(statement, scope); + } + zv::Val getStatement() const { return read(slots::statement, "statement"); } zv::Val getScope() const { return read(slots::scope, "scope"); } @@ -107,6 +115,8 @@ PT_MINIT_REGISTRATION(pt_register_internal_statement_exit_point) cls.method<&InternalStatementExitPoint::getStatement>(sigs::getStatement); + cls.method<&InternalStatementExitPoint::withScope, zp::Obj>(sigs::withScope); + cls.method<&InternalStatementExitPoint::getScope>(sigs::getScope); cls.shadow(&pt_ce_internal_statement_exit_point); diff --git a/turbo-ext/src/InternalThrowPoint.cpp b/turbo-ext/src/InternalThrowPoint.cpp index e4671cc4db1..d96e8c789ee 100644 --- a/turbo-ext/src/InternalThrowPoint.cpp +++ b/turbo-ext/src/InternalThrowPoint.cpp @@ -114,6 +114,17 @@ class InternalThrowPoint return newSelf(scope, type.raw(), node.raw(), zend_is_true(explicit_.raw()), zend_is_true(canContainAnyThrowable.raw())); } + /* Mirrors withScope(). */ + zv::Val withScope(zval *scope) const + { + zval *type = pt_typed_slot(self, slots::type, self->ce, "type"); + zval *node = type != NULL ? pt_typed_slot(self, slots::node, self->ce, "node") : NULL; + zval *explicit_ = node != NULL ? pt_typed_slot(self, slots::explicit_, self->ce, "explicit") : NULL; + zval *canContainAnyThrowable = explicit_ != NULL ? pt_typed_slot(self, slots::canContainAnyThrowable, self->ce, "canContainAnyThrowable") : NULL; + if (UNEXPECTED(canContainAnyThrowable == NULL)) return zv::Val(); + return newSelf(scope, type, node, Z_TYPE_P(explicit_) == IS_TRUE, Z_TYPE_P(canContainAnyThrowable) == IS_TRUE); + } + zv::Val getScope() const { return read(slots::scope, "scope"); } zv::Val getType() const { return read(slots::type, "type"); } zv::Val getNode() const { return read(slots::node, "node"); } @@ -214,6 +225,8 @@ PT_MINIT_REGISTRATION(pt_register_internal_throw_point) cls.method<&InternalThrowPoint::createFromPublic, zp::Obj, zp::Obj>(sigs::createFromPublic); + cls.method<&InternalThrowPoint::withScope, zp::Obj>(sigs::withScope); + cls.method<&InternalThrowPoint::getScope>(sigs::getScope); cls.method<&InternalThrowPoint::getType>(sigs::getType); diff --git a/turbo-ext/src/NodeScopeResolver.cpp b/turbo-ext/src/NodeScopeResolver.cpp index 4c78ea9b6ba..4cadeb7f040 100644 --- a/turbo-ext/src/NodeScopeResolver.cpp +++ b/turbo-ext/src/NodeScopeResolver.cpp @@ -645,6 +645,53 @@ class NodeScopeResolver return result; } + /* Mirrors processLoopFinalStmtNodes(). */ + zv::Val processLoopFinalStmtNodes(zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *lastPass) + { + zv::Val result; + pt_engine_with_stack([&]() { + zv::Val current = pt_mutating_scope_get_current_expression_result_storage(Z_OBJ_P(scope)); + if (UNEXPECTED(current.isUndef())) return; + bool pushStorage = !(current.ref().isObject() && Z_OBJ_P(current.raw()) == Z_OBJ_P(storage)); + if (pushStorage && UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(scope), storage))) return; + zval thisZv; + ZVAL_OBJ(&thisZv, self); + zval *statementsHandler = requireStatementsHandler(); + if (EXPECTED(statementsHandler != NULL)) { + result = pt_statements_handler_process_loop_final_stmt_nodes(statementsHandler, &thisZv, parentNode, stmts, scope, storage, nodeCallback, context, lastPass); + } + if (pushStorage) { + pt_finally([&]() { (void) pt_mutating_scope_pop_expression_result_storage(Z_OBJ_P(scope)); }); + if (UNEXPECTED(EG(exception))) result = zv::Val(); + } + }); + return result; + } + + /* Mirrors processLoopPassStmtNodes(); previous IS_NULL for the first + * pass, record receives this pass's [entries, recording] */ + zv::Val processLoopPassStmtNodes(zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zv::Val &record) + { + zv::Val result; + pt_engine_with_stack([&]() { + zv::Val current = pt_mutating_scope_get_current_expression_result_storage(Z_OBJ_P(scope)); + if (UNEXPECTED(current.isUndef())) return; + bool pushStorage = !(current.ref().isObject() && Z_OBJ_P(current.raw()) == Z_OBJ_P(storage)); + if (pushStorage && UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(scope), storage))) return; + zval thisZv; + ZVAL_OBJ(&thisZv, self); + zval *statementsHandler = requireStatementsHandler(); + if (EXPECTED(statementsHandler != NULL)) { + result = pt_statements_handler_process_loop_pass_stmt_nodes(statementsHandler, &thisZv, parentNode, stmts, scope, storage, nodeCallback, context, previous, record); + } + if (pushStorage) { + pt_finally([&]() { (void) pt_mutating_scope_pop_expression_result_storage(Z_OBJ_P(scope)); }); + if (UNEXPECTED(EG(exception))) result = zv::Val(); + } + }); + return result; + } + /* Mirrors processStmtNode(). */ zv::Val processStmtNode(zval *stmt, zval *scope, zval *storage, zval *nodeCallback, zval *context) { @@ -1028,6 +1075,29 @@ class NodeScopeResolver return zv::Val(std::move(empty)); } + /* Mirrors isContextSensitiveStatement(). */ + [[nodiscard]] bool isContextSensitiveStatement(zval *stmt, bool &out) + { + zv::Val cached = pt_engine_node_get_attribute(Z_OBJ_P(stmt), PT_LC("convergenceContextSensitiveStatement")); + if (UNEXPECTED(cached.isUndef())) return false; + if (!cached.isNull()) { + if (UNEXPECTED(!cached.ref().isBool())) { + zend_type_error("PHPStan\\Analyser\\NodeScopeResolver::isContextSensitiveStatement(): Return value must be of type bool, %s returned", zend_zval_value_name(cached.raw())); + return false; + } + out = cached.ref().isTrue(); + return true; + } + + bool sensitive; + if (UNEXPECTED(!hasContextSensitiveConstruct(Z_OBJ_P(stmt), sensitive))) return false; + zval value; + ZVAL_BOOL(&value, sensitive); + if (UNEXPECTED(!pt_engine_node_set_attribute(Z_OBJ_P(stmt), PT_LC("convergenceContextSensitiveStatement"), &value))) return false; + out = sensitive; + return true; + } + /* Mirrors isReplayableConvergenceBody(). */ [[nodiscard]] bool isReplayableConvergenceBody(zval *loopNode, HashTable *bodyStmts, bool &out) { @@ -2128,6 +2198,37 @@ zv::Val pt_node_scope_resolver_process_stmt_nodes_internal(zval *nodeScopeResolv return NodeScopeResolver(Z_OBJ_P(nodeScopeResolver)).thisProcessStmtNodesInternal(parentNode, stmts, scope, storage, nodeCallback, context); } +zv::Val pt_node_scope_resolver_process_loop_final_stmt_nodes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *lastPass) +{ + if (isNativeResolver(nodeScopeResolver)) return NodeScopeResolver(Z_OBJ_P(nodeScopeResolver)).processLoopFinalStmtNodes(parentNode, stmts, scope, storage, nodeCallback, context, lastPass); + zv::Args argv{parentNode, stmts, scope, storage, nodeCallback, context, lastPass}; + return pt_type_call(Z_OBJ_P(nodeScopeResolver), PT_LC("processloopfinalstmtnodes"), 7, argv); +} + +bool pt_node_scope_resolver_is_context_sensitive_statement(zval *nodeScopeResolver, zval *stmt, bool &out) +{ + if (isNativeResolver(nodeScopeResolver)) return NodeScopeResolver(Z_OBJ_P(nodeScopeResolver)).isContextSensitiveStatement(stmt, out); + zv::Val result = pt_type_call(Z_OBJ_P(nodeScopeResolver), PT_LC("iscontextsensitivestatement"), 1, stmt); + if (UNEXPECTED(result.isUndef())) return false; + out = zend_is_true(result.raw()); + return true; +} + +zv::Val pt_node_scope_resolver_process_loop_pass_stmt_nodes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zv::Val &record) +{ + if (isNativeResolver(nodeScopeResolver)) return NodeScopeResolver(Z_OBJ_P(nodeScopeResolver)).processLoopPassStmtNodes(parentNode, stmts, scope, storage, nodeCallback, context, previous, record); + zval recordRef; + ZVAL_NULL(&recordRef); + ZVAL_MAKE_REF(&recordRef); + zval null; + ZVAL_NULL(&null); + zv::Args argv{parentNode, stmts, scope, storage, nodeCallback, context, previous != NULL ? previous : &null, &recordRef}; + zv::Val result = pt_type_call(Z_OBJ_P(nodeScopeResolver), PT_LC("processlooppassstmtnodes"), 8, argv); + record = zv::Val::copyOf(zv::Ref(Z_REFVAL(recordRef))); + zval_ptr_dtor(&recordRef); + return result; +} + zv::Val pt_node_scope_resolver_process_expr_on_demand(zval *nodeScopeResolver, zval *expr, zval *scope, zval *storage) { return NodeScopeResolver(Z_OBJ_P(nodeScopeResolver)).thisProcessExprOnDemand(expr, scope, storage); @@ -2370,6 +2471,7 @@ PT_MINIT_REGISTRATION(pt_register_node_scope_resolver) cls.classConstantLong("LOOP_SCOPE_ITERATIONS", 3); cls.classConstantLong("GENERALIZE_AFTER_ITERATION", 1); cls.privateClassConstantString("REPLAYABLE_BODY_ATTRIBUTE", "convergenceReplayableBody"); + cls.privateClassConstantString("CONTEXT_SENSITIVE_STATEMENT_ATTRIBUTE", "convergenceContextSensitiveStatement"); ptdecl::NodeScopeResolver::declareProperties(cls); /* the real parameter class names: the DI container autowires the @@ -2453,6 +2555,39 @@ PT_MINIT_REGISTRATION(pt_register_node_scope_resolver) PT_RETURN_VAL(PT_NSR_THIS.processStmtNodes(parentNode, stmts, scope, nodeCallback, context)); }); + cls.method(sigs::processLoopFinalStmtNodes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *parentNode, *stmts, *scope, *storage, *nodeCallback, *context, *lastPass; + ZEND_PARSE_PARAMETERS_START(7, 7) + Z_PARAM_OBJECT(parentNode) + Z_PARAM_ARRAY(stmts) + Z_PARAM_OBJECT(scope) + Z_PARAM_OBJECT(storage) + Z_PARAM_ZVAL(nodeCallback) + Z_PARAM_OBJECT(context) + Z_PARAM_ARRAY(lastPass) + ZEND_PARSE_PARAMETERS_END(); + PT_RETURN_VAL(PT_NSR_THIS.processLoopFinalStmtNodes(parentNode, stmts, scope, storage, nodeCallback, context, lastPass)); + }); + + cls.method(sigs::processLoopPassStmtNodes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *parentNode, *stmts, *scope, *storage, *nodeCallback, *context, *previous, *recordRef; + ZEND_PARSE_PARAMETERS_START(8, 8) + Z_PARAM_OBJECT(parentNode) + Z_PARAM_ARRAY(stmts) + Z_PARAM_OBJECT(scope) + Z_PARAM_OBJECT(storage) + Z_PARAM_ZVAL(nodeCallback) + Z_PARAM_OBJECT(context) + Z_PARAM_ARRAY_OR_NULL(previous) + Z_PARAM_ZVAL(recordRef) + ZEND_PARSE_PARAMETERS_END(); + zv::Val record = zv::Val::null(); + zv::Val result = PT_NSR_THIS.processLoopPassStmtNodes(parentNode, stmts, scope, storage, nodeCallback, context, previous, record); + if (UNEXPECTED(result.isUndef())) RETURN_THROWS(); + ZEND_TRY_ASSIGN_REF_COPY(recordRef, record.raw()); + PT_RETURN_VAL(std::move(result)); + }); + cls.method(sigs::processStmtNodesInternal, [](INTERNAL_FUNCTION_PARAMETERS) { zval *parentNode, *stmts, *scope, *storage, *nodeCallback, *context; ZEND_PARSE_PARAMETERS_START(6, 6) @@ -2624,6 +2759,16 @@ PT_MINIT_REGISTRATION(pt_register_node_scope_resolver) PT_RETURN_VAL(PT_NSR_THIS.getImpurePointsFromPropertyHook(scope, propertyFetch, propertyReflection, hookName)); }); + cls.method(sigs::isContextSensitiveStatement, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *stmt; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT(stmt) + ZEND_PARSE_PARAMETERS_END(); + bool out; + if (UNEXPECTED(!PT_NSR_THIS.isContextSensitiveStatement(stmt, out))) RETURN_THROWS(); + RETURN_BOOL(out); + }); + cls.method(sigs::isReplayableConvergenceBody, [](INTERNAL_FUNCTION_PARAMETERS) { zval *loopNode; HashTable *bodyStmts; diff --git a/turbo-ext/src/StatementsHandler.cpp b/turbo-ext/src/StatementsHandler.cpp index 348df2cb700..e3134390598 100644 --- a/turbo-ext/src/StatementsHandler.cpp +++ b/turbo-ext/src/StatementsHandler.cpp @@ -656,6 +656,273 @@ class StatementsHandler return statementResult; } + /* Mirrors processLoopPassStmtNodes(): one loop convergence pass that + * re-walks only the statements whose entry differs from the previous + * pass's; $previous IS_NULL for the first pass, record receives this + * pass's [entries, recording]. UNDEF = pending exception */ + zv::Val processLoopPassStmtNodes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zv::Val &record) + { + bool isRecording = Z_TYPE_P(nodeCallback) == IS_OBJECT && instanceof_function(Z_OBJCE_P(nodeCallback), pt_ce_recording_node_callback); + return walkAgainstPreviousPass(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, previous, isRecording ? nodeCallback : NULL, false, &record); + } + + /* Mirrors processLoopFinalStmtNodes(). UNDEF = pending exception */ + zv::Val processLoopFinalStmtNodes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *lastPass) + { + return walkAgainstPreviousPass(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, lastPass, nodeCallback, true, NULL); + } + + /* Mirrors the private walkAgainstPreviousPass(): replayTarget NULL for + * none; record (a pass's [entries, recording]) is left alone by a final + * walk. UNDEF = pending exception */ + zv::Val walkAgainstPreviousPass(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zval *replayTarget, bool finalWalk, zv::Val *record) + { + bool isRecording = !finalWalk && Z_TYPE_P(nodeCallback) == IS_OBJECT && instanceof_function(Z_OBJCE_P(nodeCallback), pt_ce_recording_node_callback); + zval *recording = isRecording ? nodeCallback : NULL; + bool hasPrevious = previous != NULL && Z_TYPE_P(previous) == IS_ARRAY; + zval *previousEntries = hasPrevious ? zend_hash_index_find(Z_ARRVAL_P(previous), 0) : NULL; + zval *previousRecording = hasPrevious ? zend_hash_index_find(Z_ARRVAL_P(previous), 1) : NULL; + if (previousRecording != NULL && Z_TYPE_P(previousRecording) == IS_NULL) previousRecording = NULL; + // a replayed statement's emissions come from the previous recording; + // labels make the statement order no straight line + bool canReplay = hasPrevious && previousEntries != NULL && (replayTarget == NULL || previousRecording != NULL); + zv::Val stmtsHeld = zv::Val::copyOf(zv::Ref(stmts)); + if (canReplay) { + bool hasLabels; + if (UNEXPECTED(!containsLabels(stmtsHeld.raw(), hasLabels))) return zv::Val(); + canReplay = !hasLabels; + } + HashTable *stmtsTable = Z_ARRVAL_P(stmtsHeld.raw()); + zend_long stmtCount = zend_hash_num_elements(stmtsTable); + // the last statement a final walk walks whatever the state + zend_long lastWalked = -1; + if (canReplay && finalWalk) { + for (zend_long i = stmtCount - 1; i >= 0; i--) { + zval *stmt = zend_hash_index_find(stmtsTable, (zend_ulong) i); + if (UNEXPECTED(stmt == NULL)) { + zend_throw_error(NULL, "phpstan_turbo: the loop body's statements are not a list"); + return zv::Val(); + } + ZVAL_DEREF(stmt); + bool sensitive; + if (UNEXPECTED(!pt_node_scope_resolver_is_context_sensitive_statement(nodeScopeResolver, stmt, sensitive))) return zv::Val(); + if (sensitive) { + lastWalked = i; + break; + } + } + } + zv::Val state = pt_statement_list_walk_state_new(scope); + if (UNEXPECTED(state.isUndef())) return zv::Val(); + zv::Arr entries = zv::Arr::create(finalWalk ? 0 : (uint32_t) stmtCount + 1); + for (zend_long i = 0; i < stmtCount; i++) { + zend_long entryCount = 0; + if (!finalWalk) { + zv::Val entry = snapshotPassEntry(state.raw(), recording); + if (UNEXPECTED(entry.isUndef())) return zv::Val(); + entryCount = zval_get_long(entryOffset(entry.raw())); + entries.push(std::move(entry)); + } + zval *stmt = zend_hash_index_find(stmtsTable, (zend_ulong) i); + if (UNEXPECTED(stmt == NULL)) { + zend_throw_error(NULL, "phpstan_turbo: the loop body's statements are not a list"); + return zv::Val(); + } + ZVAL_DEREF(stmt); + bool mayReplay = canReplay; + if (mayReplay && i <= lastWalked) { + bool sensitive; + if (UNEXPECTED(!pt_node_scope_resolver_is_context_sensitive_statement(nodeScopeResolver, stmt, sensitive))) return zv::Val(); + mayReplay = !sensitive; + } + if (mayReplay) { + zval *recordedEntryPair = entryAt(previousEntries, i); + zval *recordedExitPair = recordedEntryPair != NULL ? entryAt(previousEntries, i + 1) : NULL; + if (UNEXPECTED(recordedExitPair == NULL)) return zv::Val(); + zval *recordedEntry = entryState(recordedEntryPair); + zend_long offset = zval_get_long(entryOffset(recordedEntryPair)); + zval *recordedExit = entryState(recordedExitPair); + zend_long nextOffset = zval_get_long(entryOffset(recordedExitPair)); + zend_object *stateObject = Z_OBJ_P(state.raw()); + zend_object *recordedEntryObject = Z_OBJ_P(recordedEntry); + zv::Val differingRoots = zv::Val::null(); + bool stateTerminated = Z_TYPE_P(OBJ_PROP_NUM(stateObject, stateSlots::alreadyTerminated)) == IS_TRUE; + bool entryTerminated = Z_TYPE_P(OBJ_PROP_NUM(recordedEntryObject, stateSlots::alreadyTerminated)) == IS_TRUE; + if (stateTerminated == entryTerminated) { + differingRoots = pt_mutating_scope_get_differing_variable_roots(Z_OBJ_P(OBJ_PROP_NUM(stateObject, stateSlots::scope)), Z_OBJ_P(OBJ_PROP_NUM(recordedEntryObject, stateSlots::scope))); + if (UNEXPECTED(differingRoots.isUndef())) return zv::Val(); + } + if (i > lastWalked && differingRoots.ref().isArray() && zend_hash_num_elements(Z_ARRVAL_P(differingRoots.raw())) == 0) { + // converged with the previous pass: the rest of it stands + zval *recordedEndPair = entryAt(previousEntries, stmtCount); + if (UNEXPECTED(recordedEndPair == NULL)) return zv::Val(); + zval *recordedEnd = entryState(recordedEndPair); + zend_long endOffset = zval_get_long(entryOffset(recordedEndPair)); + if (replayTarget != NULL && previousRecording != NULL && UNEXPECTED(!pt_node_scope_resolver_replay_recording_range(nodeScopeResolver, previousRecording, offset, endOffset, replayTarget, storage, scope))) return zv::Val(); + if (UNEXPECTED(!appendRecordedStatementResults(state.raw(), recordedEntry, recordedEnd))) return zv::Val(); + zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope)).assign(zv::Val::copyOf(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(recordedEnd), stateSlots::scope)))); + if (finalWalk) return pt_statement_list_walk_state_to_result(state.raw()); + // $entries[$j] = [$previous[0][$j][0], $base + $previous[0][$j][1] - $offset] for $j >= $i + zv::Arr adopted = zv::Arr::create((uint32_t) stmtCount + 1); + zend_long j = 0; + for (auto existing : zv::ArrRef(entries.raw())) { + if (j++ >= i) break; + adopted.push(zv::Val::copyOf(existing.value())); + } + for (j = i; j <= stmtCount; j++) { + zval *pair = entryAt(previousEntries, j); + if (UNEXPECTED(pair == NULL)) return zv::Val(); + zv::Arr shifted = zv::Arr::create(2); + shifted.push(zv::Val::copyOf(zv::Ref(entryState(pair)))); + shifted.push(zv::Val::integer(entryCount + zval_get_long(entryOffset(pair)) - offset)); + adopted.push(zv::Val(std::move(shifted))); + } + *record = passRecord(zv::Val(std::move(adopted)), recording); + return pt_statement_list_walk_state_to_result(state.raw()); + } + bool reWalk = differingRoots.isNull(); + if (!reWalk) { + zv::Val mentioned = withByRefClosureHolders(OBJ_PROP_NUM(stateObject, stateSlots::scope), differingRoots.raw()); + if (UNEXPECTED(mentioned.isUndef())) return zv::Val(); + if (UNEXPECTED(!statementMentionsAnyVariable(stmt, mentioned.raw(), reWalk))) return zv::Val(); + } + if (!reWalk) { + if (replayTarget != NULL && previousRecording != NULL && UNEXPECTED(!pt_node_scope_resolver_replay_recording_range(nodeScopeResolver, previousRecording, offset, nextOffset, replayTarget, storage, scope))) return zv::Val(); + zend_long exitPointCount = zend_hash_num_elements(Z_ARRVAL_P(OBJ_PROP_NUM(stateObject, stateSlots::exitPoints))); + zend_long throwPointCount = zend_hash_num_elements(Z_ARRVAL_P(OBJ_PROP_NUM(stateObject, stateSlots::throwPoints))); + if (UNEXPECTED(!appendRecordedStatementResults(state.raw(), recordedEntry, recordedExit))) return zv::Val(); + zval *recordedEntryScope = OBJ_PROP_NUM(recordedEntryObject, stateSlots::scope); + if (UNEXPECTED(!rebaseAppendedPoints(state.raw(), exitPointCount, throwPointCount, recordedEntryScope))) return zv::Val(); + zval *currentScope = OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope); + zv::Val delta = pt_mutating_scope_with_recorded_statement_delta(Z_OBJ_P(currentScope), Z_OBJ_P(recordedEntryScope), Z_OBJ_P(OBJ_PROP_NUM(Z_OBJ_P(recordedExit), stateSlots::scope))); + if (UNEXPECTED(delta.isUndef())) return zv::Val(); + zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope)).assign(std::move(delta)); + continue; + } + } + if (UNEXPECTED(!processStatementStep(nodeScopeResolver, parentNode, stmts, i, stmt, state.raw(), storage, nodeCallback, context, false))) return zv::Val(); + } + if (!finalWalk) { + zv::Val last = snapshotPassEntry(state.raw(), recording); + if (UNEXPECTED(last.isUndef())) return zv::Val(); + entries.push(std::move(last)); + *record = passRecord(zv::Val(std::move(entries)), recording); + } + return pt_statement_list_walk_state_to_result(state.raw()); + } + + /* [clone $state, $recording !== null ? $recording->count() : 0] */ + static zv::Val snapshotPassEntry(zval *state, zval *recording) + { + if (recording != NULL) return snapshotEntry(state, recording); + zv::Val cloned = cloneObject(state); + if (UNEXPECTED(cloned.isUndef())) return zv::Val(); + zv::Arr pair = zv::Arr::create(2); + pair.push(std::move(cloned)); + pair.push(zv::Val::integer(0)); + return zv::Val(std::move(pair)); + } + + /* [$entries, $recording] */ + static zv::Val passRecord(zv::Val entries, zval *recording) + { + zv::Arr pair = zv::Arr::create(2); + pair.push(std::move(entries)); + if (recording != NULL) { + pair.push(zv::Val::copyOf(zv::Ref(recording))); + } else { + pair.push(zv::Val::null()); + } + return zv::Val(std::move(pair)); + } + + /* Mirrors the private rebaseAppendedPoints(); false = pending exception */ + [[nodiscard]] static bool rebaseAppendedPoints(zval *state, zend_long exitPointCount, zend_long throwPointCount, zval *recordedEntry) + { + zend_object *stateObject = Z_OBJ_P(state); + zv::Val entry = zv::Val::copyOf(zv::Ref(OBJ_PROP_NUM(stateObject, stateSlots::scope))); + for (uint32_t pointsSlot : { stateSlots::exitPoints, stateSlots::throwPoints }) { + zend_long from = pointsSlot == stateSlots::exitPoints ? exitPointCount : throwPointCount; + zval *points = OBJ_PROP_NUM(stateObject, pointsSlot); + zend_long count = zend_hash_num_elements(Z_ARRVAL_P(points)); + for (zend_long i = from; i < count; i++) { + zval *point = zend_hash_index_find(Z_ARRVAL_P(points), (zend_ulong) i); + if (UNEXPECTED(point == NULL)) { + zend_throw_error(NULL, "phpstan_turbo: the walk state's points are not a list"); + return false; + } + zv::Val pointScope = pt_type_call(Z_OBJ_P(point), PT_LC("getscope"), 0, NULL); + if (UNEXPECTED(pointScope.isUndef())) return false; + zv::Val rebasedScope = pt_mutating_scope_with_recorded_statement_delta(Z_OBJ_P(entry.raw()), Z_OBJ_P(recordedEntry), Z_OBJ_P(pointScope.raw())); + if (UNEXPECTED(rebasedScope.isUndef())) return false; + zv::Val rebased = pt_type_call(Z_OBJ_P(point), PT_LC("withscope"), 1, rebasedScope.raw()); + if (UNEXPECTED(rebased.isUndef())) return false; + points = OBJ_PROP_NUM(stateObject, pointsSlot); + SEPARATE_ARRAY(points); + zend_hash_index_update(Z_ARRVAL_P(points), (zend_ulong) i, rebased.raw()); + ZVAL_UNDEF(rebased.raw()); + } + } + return true; + } + + /* Mirrors the private withByRefClosureHolders(); UNDEF = pending exception */ + static zv::Val withByRefClosureHolders(zval *scope, zval *variableNames) + { + zv::Val names = zv::Val::copyOf(zv::Ref(variableNames)); + if (zend_hash_num_elements(Z_ARRVAL_P(names.raw())) == 0) return names; + HashTable differing; + zend_hash_init(&differing, 8, NULL, NULL, 0); + for (auto entry : zv::ArrRef(variableNames)) { + zval *name = entry.value().deref().raw(); + if (Z_TYPE_P(name) == IS_STRING) zend_hash_add_empty_element(&differing, Z_STR_P(name)); + } + bool ok = true; + for (int kind = 0; kind < 2 && ok; kind++) { + zv::Val defined = kind == 0 ? pt_mutating_scope_get_defined_variables(Z_OBJ_P(scope)) : pt_mutating_scope_get_maybe_defined_variables(Z_OBJ_P(scope)); + if (UNEXPECTED(defined.isUndef())) { ok = false; break; } + for (auto entry : zv::ArrRef(defined.raw())) { + zval *holder = entry.value().deref().raw(); + if (Z_TYPE_P(holder) != IS_STRING || zend_hash_exists(&differing, Z_STR_P(holder))) continue; + zv::Val type = pt_mutating_scope_get_variable_type(Z_OBJ_P(scope), Z_STR_P(holder)); + if (UNEXPECTED(type.isUndef())) { ok = false; break; } + if (!type.ref().isObject()) continue; + zv::Val members; + if (instanceof_function(Z_OBJCE_P(type.raw()), pt_ce_union_type)) { + members = pt_union_type_get_types(Z_OBJ_P(type.raw())); + if (UNEXPECTED(members.isUndef())) { ok = false; break; } + } else { + zv::Arr single = zv::Arr::create(1); + single.push(zv::Val::copyOf(type.ref())); + members = zv::Val(std::move(single)); + } + bool captures = false; + for (auto member : zv::ArrRef(members.raw())) { + zval *memberType = member.value().deref().raw(); + if (Z_TYPE_P(memberType) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(memberType), pt_ce_closure_type)) continue; + zv::Val byRef = pt_closure_type_get_by_ref_use_types(memberType); + if (UNEXPECTED(byRef.isUndef())) { ok = false; break; } + for (auto use : zv::ArrRef(byRef.raw())) { + if (use.hasStringKey() && zend_hash_exists(&differing, use.stringKey())) { + captures = true; + break; + } + } + if (captures) break; + } + if (!ok) break; + if (captures) { + zv::Arr list = zv::Arr::adoptTable(zend_array_dup(Z_ARRVAL_P(names.raw()))); + list.push(zv::Ref(holder)); + names = zv::Val(std::move(list)); + } + } + if (!ok) break; + } + zend_hash_destroy(&differing); + return ok ? std::move(names) : zv::Val(); + } + /* Mirrors getVariableMentionFlow(). */ zv::Val getVariableMentionFlow(zval *stmt) { @@ -3225,6 +3492,28 @@ zv::Val pt_statements_handler_do_process_stmt_nodes(zval *handler, zval *nodeSco return pt_type_call(Z_OBJ_P(handler), PT_LC("doprocessstmtnodes"), 7, argv); } +zv::Val pt_statements_handler_process_loop_final_stmt_nodes(zval *handler, zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *lastPass) +{ + if (isNativeHandler(handler)) return StatementsHandler(Z_OBJ_P(handler)).processLoopFinalStmtNodes(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, lastPass); + zv::Args argv{nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, lastPass}; + return pt_type_call(Z_OBJ_P(handler), PT_LC("processloopfinalstmtnodes"), 8, argv); +} + +zv::Val pt_statements_handler_process_loop_pass_stmt_nodes(zval *handler, zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zv::Val &record) +{ + if (isNativeHandler(handler)) return StatementsHandler(Z_OBJ_P(handler)).processLoopPassStmtNodes(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, previous, record); + zval recordRef; + ZVAL_NULL(&recordRef); + ZVAL_MAKE_REF(&recordRef); + zval null; + ZVAL_NULL(&null); + zv::Args argv{nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, previous != NULL ? previous : &null, &recordRef}; + zv::Val result = pt_type_call(Z_OBJ_P(handler), PT_LC("processlooppassstmtnodes"), 9, argv); + record = zv::Val::copyOf(zv::Ref(Z_REFVAL(recordRef))); + zval_ptr_dtor(&recordRef); + return result; +} + zv::Val pt_statements_handler_process_stmt_var_annotation(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *defaultExpr, zval *nodeCallback) { if (isNativeHandler(handler)) return StatementsHandler(Z_OBJ_P(handler)).processStmtVarAnnotation(nodeScopeResolver, scope, storage, stmt, defaultExpr, nodeCallback); @@ -3312,6 +3601,41 @@ PT_MINIT_REGISTRATION(pt_register_statements_handler) PT_RETURN_VAL(StatementsHandler(Z_OBJ_P(ZEND_THIS)).doProcessStmtNodes(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context)); }); + cls.method(sigs::processLoopPassStmtNodes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *nodeScopeResolver, *parentNode, *stmts, *scope, *storage, *nodeCallback, *context, *previous, *recordRef; + ZEND_PARSE_PARAMETERS_START(9, 9) + Z_PARAM_OBJECT(nodeScopeResolver) + Z_PARAM_OBJECT(parentNode) + Z_PARAM_ARRAY(stmts) + Z_PARAM_OBJECT(scope) + Z_PARAM_OBJECT(storage) + Z_PARAM_ZVAL(nodeCallback) + Z_PARAM_OBJECT(context) + Z_PARAM_ARRAY_OR_NULL(previous) + Z_PARAM_ZVAL(recordRef) + ZEND_PARSE_PARAMETERS_END(); + zv::Val record = zv::Val::null(); + zv::Val result = StatementsHandler(Z_OBJ_P(ZEND_THIS)).processLoopPassStmtNodes(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, previous, record); + if (UNEXPECTED(result.isUndef())) RETURN_THROWS(); + ZEND_TRY_ASSIGN_REF_COPY(recordRef, record.raw()); + PT_RETURN_VAL(std::move(result)); + }); + + cls.method(sigs::processLoopFinalStmtNodes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *nodeScopeResolver, *parentNode, *stmts, *scope, *storage, *nodeCallback, *context, *lastPass; + ZEND_PARSE_PARAMETERS_START(8, 8) + Z_PARAM_OBJECT(nodeScopeResolver) + Z_PARAM_OBJECT(parentNode) + Z_PARAM_ARRAY(stmts) + Z_PARAM_OBJECT(scope) + Z_PARAM_OBJECT(storage) + Z_PARAM_ZVAL(nodeCallback) + Z_PARAM_OBJECT(context) + Z_PARAM_ARRAY(lastPass) + ZEND_PARSE_PARAMETERS_END(); + PT_RETURN_VAL(StatementsHandler(Z_OBJ_P(ZEND_THIS)).processLoopFinalStmtNodes(nodeScopeResolver, parentNode, stmts, scope, storage, nodeCallback, context, lastPass)); + }); + cls.method(sigs::getVariableMentionFlow, [](INTERNAL_FUNCTION_PARAMETERS) { zval *stmt; ZEND_PARSE_PARAMETERS_START(1, 1) diff --git a/turbo-ext/src/WhileHandler.cpp b/turbo-ext/src/WhileHandler.cpp index 6a1eba5fda3..d87791b76ca 100644 --- a/turbo-ext/src/WhileHandler.cpp +++ b/turbo-ext/src/WhileHandler.cpp @@ -36,6 +36,15 @@ pt_property_site pt_wh_stmts_site; } // namespace +/* the last convergence pass when it recorded its emissions (its + * [entries, recording] record), NULL otherwise */ +static zval *lastRecordedPass(zv::Val &previousPass) +{ + if (!previousPass.ref().isArray()) return NULL; + zval *recording = zend_hash_index_find(Z_ARRVAL_P(previousPass.raw()), 1); + return recording != NULL && Z_TYPE_P(recording) == IS_OBJECT ? previousPass.raw() : NULL; +} + namespace phpstanturbo { /* Mirrors PHPStan\Analyser\StmtHandler\WhileHandler; UNDEF = pending @@ -123,12 +132,16 @@ class WhileHandler zv::Val replayCondRecording, replayBodyRecording, replayCondResult, replayPassStorage, replayPassResult; zv::Val prevEntryScope; + zv::Val previousPass = zv::Val::null(); + zv::Val previousPassStorage = zv::Val::null(); if (isTopLevel) { zend_long count = 0; zval *stmts = ptsh::readNodeProperty(pt_wh_stmts_site, stmt, PT_LC("stmts")); if (UNEXPECTED(stmts == NULL)) return zv::Val(); bool bodyIsReplayable; if (UNEXPECTED(!pt_node_scope_resolver_is_replayable_convergence_body(nodeScopeResolver, stmt, stmts, bodyIsReplayable))) return zv::Val(); + // each pass after the first re-walks only the statements whose + // entry state differs from the previous pass's (StatementsHandler::processLoopPassStmtNodes()) do { zv::Val prevScope = zv::Val::copyOf(bodyScope.ref()); bodyScope = pt_mutating_scope_merge_with(Z_OBJ_P(bodyScope.raw()), scope.raw()); @@ -146,9 +159,11 @@ class WhileHandler prevEntryScope = zv::Val::copyOf(bodyScope.ref()); storage = pt_expression_result_storage_duplicate(originalStorage); if (UNEXPECTED(storage.isUndef())) return zv::Val(); + // the statements this pass replays answer from the results the previous pass stored + if (!previousPassStorage.isNull() && UNEXPECTED(!pt_expression_result_storage_merge_results(storage.raw(), previousPassStorage.raw()))) return zv::Val(); zv::Val condRecording = ptlh::newPassNodeCallback(bodyIsReplayable); if (UNEXPECTED(condRecording.isUndef())) return zv::Val(); - zv::Val bodyRecording = ptlh::newPassNodeCallback(bodyIsReplayable); + zv::Val bodyRecording = ptlh::newPassNodeCallback(true); if (UNEXPECTED(bodyRecording.isUndef())) return zv::Val(); if (UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(scope.raw()), storage.raw()))) return zv::Val(); zv::Val passCondResult, bodyScopeResult, backEdgeScope; @@ -170,8 +185,11 @@ class WhileHandler if (UNEXPECTED(deepContext.isUndef())) return; zv::Val passContext = pt_statement_context_without_template_argument_resolution(deepContext.raw()); if (UNEXPECTED(passContext.isUndef())) return; - zv::Val walked = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage.raw(), bodyRecording.raw(), passContext.raw()); + zv::Val passRecord = zv::Val::null(); + zv::Val walked = pt_node_scope_resolver_process_loop_pass_stmt_nodes(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), storage.raw(), bodyRecording.raw(), passContext.raw(), previousPass.raw(), passRecord); if (UNEXPECTED(walked.isUndef())) return; + previousPass = std::move(passRecord); + previousPassStorage = zv::Val::copyOf(storage.ref()); bodyScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); if (UNEXPECTED(bodyScopeResult.isUndef())) return; backEdgeScope = pt_internal_statement_result_loop_back_edge_scope(bodyScopeResult.raw()); @@ -234,6 +252,9 @@ class WhileHandler bodyCondResult = std::move(replayCondResult); finalScopeResult = std::move(replayPassResult); } else { + zval *lastPass = !previousPassStorage.isNull() ? lastRecordedPass(previousPass) : NULL; + // before the condition's real walk stores its own results + if (lastPass != NULL && UNEXPECTED(!pt_expression_result_storage_merge_results(originalStorage, previousPassStorage.raw()))) return zv::Val(); zval *cond = ptsh::readNodeProperty(pt_wh_cond_site, stmt, PT_LC("cond")); if (UNEXPECTED(cond == NULL)) return zv::Val(); zv::Val condHold = zv::Val::copyOf(zv::Ref(cond)); @@ -251,7 +272,11 @@ class WhileHandler zval *stmts = ptsh::readNodeProperty(pt_wh_stmts_site, stmt, PT_LC("stmts")); if (UNEXPECTED(stmts == NULL)) return zv::Val(); zv::Val stmtsHold = zv::Val::copyOf(zv::Ref(stmts)); - zv::Val walked = pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), finalStorage, nodeCallback, context); + // the last pass stands in for the statements whose state it walked + // with - they replay through the real callback + zv::Val walked = lastPass != NULL + ? pt_node_scope_resolver_process_loop_final_stmt_nodes(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), finalStorage, nodeCallback, context, lastPass) + : pt_node_scope_resolver_process_stmt_nodes_internal(nodeScopeResolver, stmt, stmtsHold.raw(), bodyScope.raw(), finalStorage, nodeCallback, context); if (UNEXPECTED(walked.isUndef())) return zv::Val(); finalScopeResult = pt_internal_statement_result_filter_out_loop_exit_points(walked.raw()); if (UNEXPECTED(finalScopeResult.isUndef())) return zv::Val(); diff --git a/turbo-ext/src/abi.h b/turbo-ext/src/abi.h index 615a8e04325..085d4b55d13 100644 --- a/turbo-ext/src/abi.h +++ b/turbo-ext/src/abi.h @@ -158,6 +158,7 @@ struct pt_abi_globals void (*call_stack_size_error)(); void (*try_assign_ref_str)(zval *zv, zend_string *str); void (*try_assign_ref_true)(zval *zv); + void (*try_assign_ref_copy)(zval *zv, zval *value); /* zend_wrong_parameter_error() with the neutral ZPP numbering (below) */ void (*wrong_parameter_error)(int errorCode, uint32_t num, char *name, int expectedType, zval *arg); /* the slow paths of the bool/double/string parameter parsing: 8.6 @@ -206,6 +207,7 @@ inline void pt_abi_weakrefs_hash_destroy(HashTable *ht) { pt_abi.weakrefs_hash_d inline void pt_abi_call_stack_size_error() { pt_abi.call_stack_size_error(); } inline void pt_abi_try_assign_ref_str(zval *zv, zend_string *str) { pt_abi.try_assign_ref_str(zv, str); } inline void pt_abi_try_assign_ref_true(zval *zv) { pt_abi.try_assign_ref_true(zv); } +inline void pt_abi_try_assign_ref_copy(zval *zv, zval *value) { pt_abi.try_assign_ref_copy(zv, value); } /* `then` from the given PHP_VERSION_ID on, `otherwise` before it — the * runtime form of an #if PHP_VERSION_ID gate */ @@ -571,6 +573,8 @@ static zend_always_inline void *pt_abi_object_alloc(size_t obj_size, zend_class_ #define ZEND_TRY_ASSIGN_REF_STR(zv, str) pt_abi_try_assign_ref_str(zv, str) #undef ZEND_TRY_ASSIGN_REF_TRUE #define ZEND_TRY_ASSIGN_REF_TRUE(zv) pt_abi_try_assign_ref_true(zv) +#undef ZEND_TRY_ASSIGN_REF_COPY +#define ZEND_TRY_ASSIGN_REF_COPY(zv, value) pt_abi_try_assign_ref_copy(zv, value) /* arg_info[i] of a user function (zend_arg_info; name and type keep their * offsets, the stride moved) */ diff --git a/turbo-ext/src/generated/ForeachEmptyOrObjectNarrowingHelper.h b/turbo-ext/src/generated/ForeachEmptyOrObjectNarrowingHelper.h new file mode 100644 index 00000000000..4a9725736e2 --- /dev/null +++ b/turbo-ext/src/generated/ForeachEmptyOrObjectNarrowingHelper.h @@ -0,0 +1,109 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Analyser/ExprHandler/Helper/ForeachEmptyOrObjectNarrowingHelper.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_FOREACH_EMPTY_OR_OBJECT_NARROWING_HELPER_H +#define PHPSTANTURBO_GENERATED_FOREACH_EMPTY_OR_OBJECT_NARROWING_HELPER_H + +#include "../reg.h" + +namespace ptdecl::ForeachEmptyOrObjectNarrowingHelper { + +/* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ +namespace slot { +inline constexpr uint32_t reflectionProvider = 0; +inline constexpr uint32_t identicalNarrowingHelper = 1; +inline constexpr uint32_t defaultNarrowingHelper = 2; +inline constexpr uint32_t booleanNarrowingHelper = 3; +} // namespace slot + +inline void declareClass(reg::Class &cls) +{ + cls.final(); +} + +/* the properties the class declares itself, in declaration order (a used trait's come from its registrar) */ +inline void declareProperties(reg::Class &cls) +{ + cls.property("reflectionProvider", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Reflection\\ReflectionProvider"); + cls.property("identicalNarrowingHelper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExprHandler\\Helper\\IdenticalNarrowingHelper"); + cls.property("defaultNarrowingHelper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExprHandler\\Helper\\DefaultNarrowingHelper"); + cls.property("booleanNarrowingHelper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExprHandler\\Helper\\BooleanNarrowingHelper"); +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "reflectionProvider\0" /* 0 */ + "PHPStan\\Reflection\\ReflectionProvider\0" /* 19 */ + "identicalNarrowingHelper\0" /* 57 */ + "PHPStan\\Analyser\\ExprHandler\\Helper\\IdenticalNarrowingHelper\0" /* 82 */ + "defaultNarrowingHelper\0" /* 143 */ + "PHPStan\\Analyser\\ExprHandler\\Helper\\DefaultNarrowingHelper\0" /* 166 */ + "booleanNarrowingHelper\0" /* 225 */ + "PHPStan\\Analyser\\ExprHandler\\Helper\\BooleanNarrowingHelper\0" /* 248 */ + "__construct\0" /* 307 */ + "nodeScopeResolver\0" /* 319 */ + "PHPStan\\Analyser\\NodeScopeResolver\0" /* 337 */ + "scope\0" /* 372 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 378 */ + "iteratee\0" /* 409 */ + "PhpParser\\Node\\Expr\0" /* 418 */ + "iterateeResult\0" /* 438 */ + "PHPStan\\Analyser\\ExpressionResult\0" /* 453 */ + "storage\0" /* 487 */ + "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 495 */ + "\0" /* 536 */ + "narrowToEmptyOrObject\0" /* 537 */ + "condition\0" /* 559 */ + "PhpParser\\Node\\Expr\\BinaryOp\\BooleanOr\0" /* 569 */ + "identical\0" /* 608 */ + "PhpParser\\Node\\Expr\\BinaryOp\\Identical\0" /* 618 */ + "isObjectCall\0" /* 657 */ + "PhpParser\\Node\\Expr\\FuncCall\0" /* 670 */ + "composeScope\0" /* 699 */ + "iterateeType\0" /* 712 */ + "PHPStan\\Type\\Type\0" /* 725 */ + "emptyArrayType\0" /* 743 */ + "PHPStan\\Type\\Constant\\ConstantArrayType\0" /* 758 */ + "emptyArrayComparison\0" /* 798 */ + "isObjectVerdict"; /* 819 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, 0, 19), /* __construct $reflectionProvider */ + reg::packed(57, 0, 82), /* __construct $identicalNarrowingHelper */ + reg::packed(143, 0, 166), /* __construct $defaultNarrowingHelper */ + reg::packed(225, 0, 248), /* __construct $booleanNarrowingHelper */ + reg::packed(319, 0, 337), /* narrowToEmptyOrObject $nodeScopeResolver */ + reg::packed(372, 0, 378), /* narrowToEmptyOrObject $scope */ + reg::packed(409, 0, 418), /* narrowToEmptyOrObject $iteratee */ + reg::packed(438, 0, 453), /* narrowToEmptyOrObject $iterateeResult */ + reg::packed(487, 0, 495), /* narrowToEmptyOrObject $storage */ + reg::packed(536, 0, 378), /* narrowToEmptyOrObject return */ + reg::packed(319, 0, 337), /* composeScope $nodeScopeResolver */ + reg::packed(372, 0, 378), /* composeScope $scope */ + reg::packed(559, 0, 569), /* composeScope $condition */ + reg::packed(608, 0, 618), /* composeScope $identical */ + reg::packed(657, 0, 670), /* composeScope $isObjectCall */ + reg::packed(438, 0, 453), /* composeScope $iterateeResult */ + reg::packed(487, 0, 495), /* composeScope $storage */ + reg::packed(536, MAY_BE_NULL, 378), /* composeScope return */ + reg::packed(712, 0, 725), /* emptyArrayComparison $iterateeType */ + reg::packed(743, 0, 758), /* emptyArrayComparison $emptyArrayType */ + reg::packed(536, 0, 725), /* emptyArrayComparison return */ + reg::packed(712, 0, 725), /* isObjectVerdict $iterateeType */ + reg::packed(536, 0, 725), /* isObjectVerdict return */ +}; +using Sig = reg::Sig; +} // namespace sigtab + +/* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ +namespace sig { +inline constexpr sigtab::Sig __construct = { { 307 /* __construct */, 4, 0, 4, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig narrowToEmptyOrObject = { { 537 /* narrowToEmptyOrObject */, 5, 4, 5, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig composeScope = { { 699 /* composeScope */, 7, 10, 7, 17, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig emptyArrayComparison = { { 798 /* emptyArrayComparison */, 2, 18, 2, 20, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig isObjectVerdict = { { 819 /* isObjectVerdict */, 1, 21, 1, 22, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +} // namespace sig + +} // namespace ptdecl::ForeachEmptyOrObjectNarrowingHelper + +#endif diff --git a/turbo-ext/src/generated/InternalStatementExitPoint.h b/turbo-ext/src/generated/InternalStatementExitPoint.h index fe233feea32..c10a225e970 100644 --- a/turbo-ext/src/generated/InternalStatementExitPoint.h +++ b/turbo-ext/src/generated/InternalStatementExitPoint.h @@ -38,12 +38,16 @@ inline constexpr char strings[] = "PHPStan\\Analyser\\StatementExitPoint\0" /* 80 */ "toPublic\0" /* 116 */ "getStatement\0" /* 125 */ - "getScope"; /* 138 */ + "PHPStan\\Analyser\\InternalStatementExitPoint\0" /* 138 */ + "withScope\0" /* 182 */ + "getScope"; /* 192 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 10), /* __construct $statement */ reg::packed(30, 0, 36), /* __construct $scope */ reg::packed(79, 0, 80), /* toPublic return */ reg::packed(79, 0, 10), /* getStatement return */ + reg::packed(30, 0, 36), /* withScope $scope */ + reg::packed(79, 0, 138), /* withScope return */ reg::packed(79, 0, 36), /* getScope return */ }; using Sig = reg::Sig; @@ -54,7 +58,8 @@ namespace sig { inline constexpr sigtab::Sig __construct = { { 67 /* __construct */, 2, 0, 2, reg::NoArg, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig toPublic = { { 116 /* toPublic */, 0, 2, 0, 2, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getStatement = { { 125 /* getStatement */, 0, 3, 0, 3, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getScope = { { 138 /* getScope */, 0, 4, 0, 4, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withScope = { { 182 /* withScope */, 1, 4, 1, 5, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getScope = { { 192 /* getScope */, 0, 6, 0, 6, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::InternalStatementExitPoint diff --git a/turbo-ext/src/generated/InternalThrowPoint.h b/turbo-ext/src/generated/InternalThrowPoint.h index 351d62a242e..9dd4fd7bfad 100644 --- a/turbo-ext/src/generated/InternalThrowPoint.h +++ b/turbo-ext/src/generated/InternalThrowPoint.h @@ -53,12 +53,13 @@ inline constexpr char strings[] = "createImplicit\0" /* 218 */ "throwPoint\0" /* 233 */ "createFromPublic\0" /* 244 */ - "getScope\0" /* 261 */ - "getType\0" /* 270 */ - "getNode\0" /* 278 */ - "isExplicit\0" /* 286 */ - "catchType\0" /* 297 */ - "subtractCatchType"; /* 307 */ + "withScope\0" /* 261 */ + "getScope\0" /* 271 */ + "getType\0" /* 280 */ + "getNode\0" /* 288 */ + "isExplicit\0" /* 296 */ + "catchType\0" /* 307 */ + "subtractCatchType"; /* 317 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 6), /* __construct $scope */ reg::packed(37, 0, 42), /* __construct $type */ @@ -78,11 +79,13 @@ inline constexpr reg::PackedArg args[] = { reg::packed(233, 0, 125), /* createFromPublic $throwPoint */ reg::packed(0, 0, 6), /* createFromPublic $scope */ reg::packed(124, 0, 162), /* createFromPublic return */ + reg::packed(0, 0, 6), /* withScope $scope */ + reg::packed(124, 0, 162), /* withScope return */ reg::packed(124, 0, 6), /* getScope return */ reg::packed(124, 0, 42), /* getType return */ reg::packed(124, MAY_BE_BOOL), /* isExplicit return */ reg::packed(124, MAY_BE_BOOL), /* canContainAnyThrowable return */ - reg::packed(297, 0, 42), /* subtractCatchType $catchType */ + reg::packed(307, 0, 42), /* subtractCatchType $catchType */ reg::packed(124, 0, 162), /* subtractCatchType return */ }; using Sig = reg::Sig; @@ -95,12 +98,13 @@ inline constexpr sigtab::Sig toPublic = { { 153 /* toPublic */, 0, 5, 0, 5, ZEND inline constexpr sigtab::Sig createExplicit = { { 198 /* createExplicit */, 4, 6, 4, 10, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig createImplicit = { { 218 /* createImplicit */, 2, 11, 3, 14, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; inline constexpr sigtab::Sig createFromPublic = { { 244 /* createFromPublic */, 2, 15, 2, 17, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getScope = { { 261 /* getScope */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getType = { { 270 /* getType */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getNode = { { 278 /* getNode */, 0, 20, 0, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isExplicit = { { 286 /* isExplicit */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canContainAnyThrowable = { { 89 /* canContainAnyThrowable */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig subtractCatchType = { { 307 /* subtractCatchType */, 1, 22, 1, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withScope = { { 261 /* withScope */, 1, 18, 1, 19, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getScope = { { 271 /* getScope */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getType = { { 280 /* getType */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getNode = { { 288 /* getNode */, 0, 22, 0, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isExplicit = { { 296 /* isExplicit */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canContainAnyThrowable = { { 89 /* canContainAnyThrowable */, 0, 23, 0, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig subtractCatchType = { { 317 /* subtractCatchType */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::InternalThrowPoint diff --git a/turbo-ext/src/generated/NodeScopeResolver.h b/turbo-ext/src/generated/NodeScopeResolver.h index 36b977e4b2a..754c18e3fcb 100644 --- a/turbo-ext/src/generated/NodeScopeResolver.h +++ b/turbo-ext/src/generated/NodeScopeResolver.h @@ -92,72 +92,78 @@ inline constexpr char strings[] = "PHPStan\\Analyser\\StatementContext\0" /* 864 */ "PHPStan\\Analyser\\StatementResult\0" /* 898 */ "processStmtNodes\0" /* 931 */ - "PHPStan\\Analyser\\InternalStatementResult\0" /* 948 */ - "processStmtNodesInternal\0" /* 989 */ - "stmt\0" /* 1014 */ - "PhpParser\\Node\\Stmt\0" /* 1019 */ - "processStmtNode\0" /* 1039 */ - "fileName\0" /* 1055 */ - "isAnalysedFile\0" /* 1064 */ - "isReturningStoredExpressionResults\0" /* 1079 */ - "isConsumingStoredExpressionResults\0" /* 1114 */ - "lookForSetAllowedUndefinedExpressions\0" /* 1149 */ - "lookForUnsetAllowedUndefinedExpressions\0" /* 1187 */ - "callback\0" /* 1227 */ - "Closure\0" /* 1236 */ - "lookForExpressionCallback\0" /* 1244 */ - "PHPStan\\Analyser\\ExpressionContext\0" /* 1270 */ - "processExprNodeConsumingStored\0" /* 1305 */ - "processExprOnDemand\0" /* 1336 */ - "readStoredResult\0" /* 1356 */ - "PHPStan\\Type\\Type\0" /* 1373 */ - "readTypeOfMaybeStored\0" /* 1391 */ - "findScopeStateType\0" /* 1413 */ - "readScopeStateOrSyntheticType\0" /* 1432 */ - "requireScopeStateType\0" /* 1462 */ - "caller\0" /* 1484 */ - "guardAgainstUnprocessedRealNode\0" /* 1491 */ - "processSyntheticOnDemand\0" /* 1523 */ - "processExprNode\0" /* 1548 */ - "processExprNodeInternal\0" /* 1564 */ - "propertyFetch\0" /* 1588 */ - "PhpParser\\Node\\Expr\\PropertyFetch\0" /* 1602 */ - "propertyReflection\0" /* 1636 */ - "PHPStan\\Reflection\\Php\\PhpPropertyReflection\0" /* 1655 */ - "hookName\0" /* 1700 */ - "getImpurePointsFromPropertyHook\0" /* 1709 */ - "isPropertyHookBackingValueAccess\0" /* 1741 */ - "getAssignedVariables\0" /* 1774 */ - "loopNode\0" /* 1795 */ - "bodyStmts\0" /* 1804 */ - "isReplayableConvergenceBody\0" /* 1814 */ - "node\0" /* 1842 */ - "hasContextSensitiveConstruct\0" /* 1847 */ - "gatherer\0" /* 1876 */ - "pushNodeGatherer\0" /* 1885 */ - "popNodeGatherer\0" /* 1902 */ - "suspendNodeGatherers\0" /* 1918 */ - "gatherers\0" /* 1939 */ - "restoreNodeGatherers\0" /* 1949 */ - "recording\0" /* 1970 */ - "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1980 */ - "replayRecording\0" /* 2019 */ - "from\0" /* 2035 */ - "to\0" /* 2040 */ - "replayRecordingRange\0" /* 2043 */ - "callNodeCallbackWithExpression\0" /* 2064 */ - "callNodeCallback\0" /* 2095 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 2112 */ - "observingTemplateArgumentFrame\0" /* 2160 */ - "returnedResult\0" /* 2191 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 2206 */ - "collectReturnSend\0" /* 2260 */ - "keyType\0" /* 2278 */ - "valueType\0" /* 2286 */ - "collectYieldSend\0" /* 2296 */ - "delegatedType\0" /* 2313 */ - "collectYieldFromSend\0" /* 2327 */ - "getDeclaredReturnType"; /* 2348 */ + "lastPass\0" /* 948 */ + "PHPStan\\Analyser\\InternalStatementResult\0" /* 957 */ + "processLoopFinalStmtNodes\0" /* 998 */ + "previous\0" /* 1024 */ + "record\0" /* 1033 */ + "processLoopPassStmtNodes\0" /* 1040 */ + "processStmtNodesInternal\0" /* 1065 */ + "stmt\0" /* 1090 */ + "PhpParser\\Node\\Stmt\0" /* 1095 */ + "processStmtNode\0" /* 1115 */ + "fileName\0" /* 1131 */ + "isAnalysedFile\0" /* 1140 */ + "isReturningStoredExpressionResults\0" /* 1155 */ + "isConsumingStoredExpressionResults\0" /* 1190 */ + "lookForSetAllowedUndefinedExpressions\0" /* 1225 */ + "lookForUnsetAllowedUndefinedExpressions\0" /* 1263 */ + "callback\0" /* 1303 */ + "Closure\0" /* 1312 */ + "lookForExpressionCallback\0" /* 1320 */ + "PHPStan\\Analyser\\ExpressionContext\0" /* 1346 */ + "processExprNodeConsumingStored\0" /* 1381 */ + "processExprOnDemand\0" /* 1412 */ + "readStoredResult\0" /* 1432 */ + "PHPStan\\Type\\Type\0" /* 1449 */ + "readTypeOfMaybeStored\0" /* 1467 */ + "findScopeStateType\0" /* 1489 */ + "readScopeStateOrSyntheticType\0" /* 1508 */ + "requireScopeStateType\0" /* 1538 */ + "caller\0" /* 1560 */ + "guardAgainstUnprocessedRealNode\0" /* 1567 */ + "processSyntheticOnDemand\0" /* 1599 */ + "processExprNode\0" /* 1624 */ + "processExprNodeInternal\0" /* 1640 */ + "propertyFetch\0" /* 1664 */ + "PhpParser\\Node\\Expr\\PropertyFetch\0" /* 1678 */ + "propertyReflection\0" /* 1712 */ + "PHPStan\\Reflection\\Php\\PhpPropertyReflection\0" /* 1731 */ + "hookName\0" /* 1776 */ + "getImpurePointsFromPropertyHook\0" /* 1785 */ + "isPropertyHookBackingValueAccess\0" /* 1817 */ + "getAssignedVariables\0" /* 1850 */ + "loopNode\0" /* 1871 */ + "bodyStmts\0" /* 1880 */ + "isReplayableConvergenceBody\0" /* 1890 */ + "isContextSensitiveStatement\0" /* 1918 */ + "node\0" /* 1946 */ + "hasContextSensitiveConstruct\0" /* 1951 */ + "gatherer\0" /* 1980 */ + "pushNodeGatherer\0" /* 1989 */ + "popNodeGatherer\0" /* 2006 */ + "suspendNodeGatherers\0" /* 2022 */ + "gatherers\0" /* 2043 */ + "restoreNodeGatherers\0" /* 2053 */ + "recording\0" /* 2074 */ + "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 2084 */ + "replayRecording\0" /* 2123 */ + "from\0" /* 2139 */ + "to\0" /* 2144 */ + "replayRecordingRange\0" /* 2147 */ + "callNodeCallbackWithExpression\0" /* 2168 */ + "callNodeCallback\0" /* 2199 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 2216 */ + "observingTemplateArgumentFrame\0" /* 2264 */ + "returnedResult\0" /* 2295 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 2310 */ + "collectReturnSend\0" /* 2364 */ + "keyType\0" /* 2382 */ + "valueType\0" /* 2390 */ + "collectYieldSend\0" /* 2400 */ + "delegatedType\0" /* 2417 */ + "collectYieldFromSend\0" /* 2431 */ + "getDeclaredReturnType"; /* 2452 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 10), /* __construct $container */ reg::packed(48, 0, 73), /* __construct $templateArgumentObserver */ @@ -191,20 +197,37 @@ inline constexpr reg::PackedArg args[] = { reg::packed(539, MAY_BE_CALLABLE), /* processStmtNodes $nodeCallback */ reg::packed(761, 0, 864), /* processStmtNodes $context */ reg::packed(371, 0, 898), /* processStmtNodes return */ + reg::packed(832, 0, 843), /* processLoopFinalStmtNodes $parentNode */ + reg::packed(858, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $stmts */ + reg::packed(502, 0, 508), /* processLoopFinalStmtNodes $scope */ + reg::packed(614, 0, 622), /* processLoopFinalStmtNodes $storage */ + reg::packed(539, MAY_BE_CALLABLE), /* processLoopFinalStmtNodes $nodeCallback */ + reg::packed(761, 0, 864), /* processLoopFinalStmtNodes $context */ + reg::packed(948, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $lastPass */ + reg::packed(371, 0, 957), /* processLoopFinalStmtNodes return */ + reg::packed(832, 0, 843), /* processLoopPassStmtNodes $parentNode */ + reg::packed(858, MAY_BE_ARRAY), /* processLoopPassStmtNodes $stmts */ + reg::packed(502, 0, 508), /* processLoopPassStmtNodes $scope */ + reg::packed(614, 0, 622), /* processLoopPassStmtNodes $storage */ + reg::packed(539, MAY_BE_CALLABLE), /* processLoopPassStmtNodes $nodeCallback */ + reg::packed(761, 0, 864), /* processLoopPassStmtNodes $context */ + reg::packed(1024, MAY_BE_NULL | MAY_BE_ARRAY), /* processLoopPassStmtNodes $previous */ + reg::packed(1033, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, true, false), /* processLoopPassStmtNodes $record */ + reg::packed(371, 0, 957), /* processLoopPassStmtNodes return */ reg::packed(832, 0, 843), /* processStmtNodesInternal $parentNode */ reg::packed(858, MAY_BE_ARRAY), /* processStmtNodesInternal $stmts */ reg::packed(502, 0, 508), /* processStmtNodesInternal $scope */ reg::packed(614, 0, 622), /* processStmtNodesInternal $storage */ reg::packed(539, MAY_BE_CALLABLE), /* processStmtNodesInternal $nodeCallback */ reg::packed(761, 0, 864), /* processStmtNodesInternal $context */ - reg::packed(371, 0, 948), /* processStmtNodesInternal return */ - reg::packed(1014, 0, 1019), /* processStmtNode $stmt */ + reg::packed(371, 0, 957), /* processStmtNodesInternal return */ + reg::packed(1090, 0, 1095), /* processStmtNode $stmt */ reg::packed(502, 0, 508), /* processStmtNode $scope */ reg::packed(614, 0, 622), /* processStmtNode $storage */ reg::packed(539, MAY_BE_CALLABLE), /* processStmtNode $nodeCallback */ reg::packed(761, 0, 864), /* processStmtNode $context */ - reg::packed(371, 0, 948), /* processStmtNode return */ - reg::packed(1055, MAY_BE_STRING), /* isAnalysedFile $fileName */ + reg::packed(371, 0, 957), /* processStmtNode return */ + reg::packed(1131, MAY_BE_STRING), /* isAnalysedFile $fileName */ reg::packed(371, MAY_BE_BOOL), /* isAnalysedFile return */ reg::packed(371, MAY_BE_BOOL), /* isReturningStoredExpressionResults return */ reg::packed(371, MAY_BE_BOOL), /* isConsumingStoredExpressionResults return */ @@ -216,14 +239,14 @@ inline constexpr reg::PackedArg args[] = { reg::packed(371, 0, 508), /* lookForUnsetAllowedUndefinedExpressions return */ reg::packed(502, 0, 508), /* lookForExpressionCallback $scope */ reg::packed(663, 0, 668), /* lookForExpressionCallback $expr */ - reg::packed(1227, 0, 1236), /* lookForExpressionCallback $callback */ + reg::packed(1303, 0, 1312), /* lookForExpressionCallback $callback */ reg::packed(371, 0, 508), /* lookForExpressionCallback return */ - reg::packed(1014, 0, 1019), /* processExprNodeConsumingStored $stmt */ + reg::packed(1090, 0, 1095), /* processExprNodeConsumingStored $stmt */ reg::packed(663, 0, 668), /* processExprNodeConsumingStored $expr */ reg::packed(502, 0, 508), /* processExprNodeConsumingStored $scope */ reg::packed(614, 0, 622), /* processExprNodeConsumingStored $storage */ reg::packed(539, MAY_BE_CALLABLE), /* processExprNodeConsumingStored $nodeCallback */ - reg::packed(761, 0, 1270), /* processExprNodeConsumingStored $context */ + reg::packed(761, 0, 1346), /* processExprNodeConsumingStored $context */ reg::packed(371, 0, 705), /* processExprNodeConsumingStored return */ reg::packed(663, 0, 668), /* processExprOnDemand $expr */ reg::packed(502, 0, 508), /* processExprOnDemand $scope */ @@ -234,65 +257,67 @@ inline constexpr reg::PackedArg args[] = { reg::packed(371, 0, 705), /* readStoredResult return */ reg::packed(663, 0, 668), /* readTypeOfMaybeStored $expr */ reg::packed(502, 0, 508), /* readTypeOfMaybeStored $scope */ - reg::packed(371, 0, 1373), /* readTypeOfMaybeStored return */ + reg::packed(371, 0, 1449), /* readTypeOfMaybeStored return */ reg::packed(663, 0, 668), /* findScopeStateType $expr */ reg::packed(502, 0, 508), /* findScopeStateType $scope */ - reg::packed(371, MAY_BE_NULL, 1373), /* findScopeStateType return */ + reg::packed(371, MAY_BE_NULL, 1449), /* findScopeStateType return */ reg::packed(663, 0, 668), /* readScopeStateOrSyntheticType $expr */ reg::packed(502, 0, 508), /* readScopeStateOrSyntheticType $scope */ - reg::packed(371, 0, 1373), /* readScopeStateOrSyntheticType return */ + reg::packed(371, 0, 1449), /* readScopeStateOrSyntheticType return */ reg::packed(663, 0, 668), /* requireScopeStateType $expr */ reg::packed(502, 0, 508), /* requireScopeStateType $scope */ - reg::packed(371, 0, 1373), /* requireScopeStateType return */ + reg::packed(371, 0, 1449), /* requireScopeStateType return */ reg::packed(663, 0, 668), /* guardAgainstUnprocessedRealNode $expr */ - reg::packed(1484, MAY_BE_STRING), /* guardAgainstUnprocessedRealNode $caller */ + reg::packed(1560, MAY_BE_STRING), /* guardAgainstUnprocessedRealNode $caller */ reg::packed(371, MAY_BE_VOID), /* guardAgainstUnprocessedRealNode return */ reg::packed(663, 0, 668), /* processSyntheticOnDemand $expr */ reg::packed(502, 0, 508), /* processSyntheticOnDemand $scope */ reg::packed(371, 0, 705), /* processSyntheticOnDemand return */ - reg::packed(1014, 0, 1019), /* processExprNode $stmt */ + reg::packed(1090, 0, 1095), /* processExprNode $stmt */ reg::packed(663, 0, 668), /* processExprNode $expr */ reg::packed(502, 0, 508), /* processExprNode $scope */ reg::packed(614, 0, 622), /* processExprNode $storage */ reg::packed(539, MAY_BE_CALLABLE), /* processExprNode $nodeCallback */ - reg::packed(761, 0, 1270), /* processExprNode $context */ + reg::packed(761, 0, 1346), /* processExprNode $context */ reg::packed(371, 0, 705), /* processExprNode return */ - reg::packed(1014, 0, 1019), /* processExprNodeInternal $stmt */ + reg::packed(1090, 0, 1095), /* processExprNodeInternal $stmt */ reg::packed(663, 0, 668), /* processExprNodeInternal $expr */ reg::packed(502, 0, 508), /* processExprNodeInternal $scope */ reg::packed(614, 0, 622), /* processExprNodeInternal $storage */ reg::packed(539, MAY_BE_CALLABLE), /* processExprNodeInternal $nodeCallback */ - reg::packed(761, 0, 1270), /* processExprNodeInternal $context */ + reg::packed(761, 0, 1346), /* processExprNodeInternal $context */ reg::packed(371, 0, 705), /* processExprNodeInternal return */ reg::packed(502, 0, 508), /* getImpurePointsFromPropertyHook $scope */ - reg::packed(1588, 0, 1602), /* getImpurePointsFromPropertyHook $propertyFetch */ - reg::packed(1636, 0, 1655), /* getImpurePointsFromPropertyHook $propertyReflection */ - reg::packed(1700, MAY_BE_STRING), /* getImpurePointsFromPropertyHook $hookName */ + reg::packed(1664, 0, 1678), /* getImpurePointsFromPropertyHook $propertyFetch */ + reg::packed(1712, 0, 1731), /* getImpurePointsFromPropertyHook $propertyReflection */ + reg::packed(1776, MAY_BE_STRING), /* getImpurePointsFromPropertyHook $hookName */ reg::packed(371, MAY_BE_ARRAY), /* getImpurePointsFromPropertyHook return */ reg::packed(502, 0, 508), /* isPropertyHookBackingValueAccess $scope */ - reg::packed(1588, 0, 1602), /* isPropertyHookBackingValueAccess $propertyFetch */ + reg::packed(1664, 0, 1678), /* isPropertyHookBackingValueAccess $propertyFetch */ reg::packed(371, MAY_BE_BOOL), /* isPropertyHookBackingValueAccess return */ reg::packed(663, 0, 668), /* getAssignedVariables $expr */ reg::packed(371, MAY_BE_ARRAY), /* getAssignedVariables return */ - reg::packed(1795, 0, 843), /* isReplayableConvergenceBody $loopNode */ - reg::packed(1804, MAY_BE_ARRAY), /* isReplayableConvergenceBody $bodyStmts */ + reg::packed(1871, 0, 843), /* isReplayableConvergenceBody $loopNode */ + reg::packed(1880, MAY_BE_ARRAY), /* isReplayableConvergenceBody $bodyStmts */ reg::packed(371, MAY_BE_BOOL), /* isReplayableConvergenceBody return */ - reg::packed(1842, 0, 843), /* hasContextSensitiveConstruct $node */ + reg::packed(1090, 0, 1095), /* isContextSensitiveStatement $stmt */ + reg::packed(371, MAY_BE_BOOL), /* isContextSensitiveStatement return */ + reg::packed(1946, 0, 843), /* hasContextSensitiveConstruct $node */ reg::packed(371, MAY_BE_BOOL), /* hasContextSensitiveConstruct return */ - reg::packed(1876, MAY_BE_CALLABLE), /* pushNodeGatherer $gatherer */ + reg::packed(1980, MAY_BE_CALLABLE), /* pushNodeGatherer $gatherer */ reg::packed(371, MAY_BE_VOID), /* pushNodeGatherer return */ reg::packed(371, MAY_BE_VOID), /* popNodeGatherer return */ reg::packed(371, MAY_BE_ARRAY), /* suspendNodeGatherers return */ - reg::packed(1939, MAY_BE_ARRAY), /* restoreNodeGatherers $gatherers */ + reg::packed(2043, MAY_BE_ARRAY), /* restoreNodeGatherers $gatherers */ reg::packed(371, MAY_BE_VOID), /* restoreNodeGatherers return */ - reg::packed(1970, 0, 1980), /* replayRecording $recording */ + reg::packed(2074, 0, 2084), /* replayRecording $recording */ reg::packed(539, MAY_BE_CALLABLE), /* replayRecording $nodeCallback */ reg::packed(614, 0, 622), /* replayRecording $storage */ reg::packed(502, 0, 508), /* replayRecording $scope */ reg::packed(371, MAY_BE_VOID), /* replayRecording return */ - reg::packed(1970, 0, 1980), /* replayRecordingRange $recording */ - reg::packed(2035, MAY_BE_LONG), /* replayRecordingRange $from */ - reg::packed(2040, MAY_BE_LONG), /* replayRecordingRange $to */ + reg::packed(2074, 0, 2084), /* replayRecordingRange $recording */ + reg::packed(2139, MAY_BE_LONG), /* replayRecordingRange $from */ + reg::packed(2144, MAY_BE_LONG), /* replayRecordingRange $to */ reg::packed(539, MAY_BE_CALLABLE), /* replayRecordingRange $nodeCallback */ reg::packed(614, 0, 622), /* replayRecordingRange $storage */ reg::packed(502, 0, 508), /* replayRecordingRange $scope */ @@ -301,27 +326,27 @@ inline constexpr reg::PackedArg args[] = { reg::packed(663, 0, 843), /* callNodeCallbackWithExpression $expr */ reg::packed(502, 0, 508), /* callNodeCallbackWithExpression $scope */ reg::packed(614, 0, 622), /* callNodeCallbackWithExpression $storage */ - reg::packed(761, 0, 1270), /* callNodeCallbackWithExpression $context */ + reg::packed(761, 0, 1346), /* callNodeCallbackWithExpression $context */ reg::packed(371, MAY_BE_VOID), /* callNodeCallbackWithExpression return */ reg::packed(539, MAY_BE_CALLABLE), /* callNodeCallback $nodeCallback */ - reg::packed(1842, 0, 843), /* callNodeCallback $node */ + reg::packed(1946, 0, 843), /* callNodeCallback $node */ reg::packed(502, 0, 508), /* callNodeCallback $scope */ reg::packed(614, 0, 622), /* callNodeCallback $storage */ reg::packed(371, MAY_BE_VOID), /* callNodeCallback return */ reg::packed(502, 0, 508), /* observingTemplateArgumentFrame $scope */ - reg::packed(371, MAY_BE_NULL, 2112), /* observingTemplateArgumentFrame return */ + reg::packed(371, MAY_BE_NULL, 2216), /* observingTemplateArgumentFrame return */ reg::packed(502, 0, 508), /* collectReturnSend $scope */ - reg::packed(2191, 0, 705), /* collectReturnSend $returnedResult */ - reg::packed(371, 0, 2206), /* collectReturnSend return */ + reg::packed(2295, 0, 705), /* collectReturnSend $returnedResult */ + reg::packed(371, 0, 2310), /* collectReturnSend return */ reg::packed(502, 0, 508), /* collectYieldSend $scope */ - reg::packed(2278, MAY_BE_NULL, 1373), /* collectYieldSend $keyType */ - reg::packed(2286, MAY_BE_NULL, 1373), /* collectYieldSend $valueType */ - reg::packed(371, 0, 2206), /* collectYieldSend return */ + reg::packed(2382, MAY_BE_NULL, 1449), /* collectYieldSend $keyType */ + reg::packed(2390, MAY_BE_NULL, 1449), /* collectYieldSend $valueType */ + reg::packed(371, 0, 2310), /* collectYieldSend return */ reg::packed(502, 0, 508), /* collectYieldFromSend $scope */ - reg::packed(2313, 0, 1373), /* collectYieldFromSend $delegatedType */ - reg::packed(371, 0, 2206), /* collectYieldFromSend return */ + reg::packed(2417, 0, 1449), /* collectYieldFromSend $delegatedType */ + reg::packed(371, 0, 2310), /* collectYieldFromSend return */ reg::packed(502, 0, 508), /* getDeclaredReturnType $scope */ - reg::packed(371, MAY_BE_NULL, 1373), /* getDeclaredReturnType return */ + reg::packed(371, MAY_BE_NULL, 1449), /* getDeclaredReturnType return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -337,43 +362,46 @@ inline constexpr sigtab::Sig processFileNodes = { { 597 /* processFileNodes */, inline constexpr sigtab::Sig storeExpressionResult = { { 739 /* storeExpressionResult */, 3, 18, 3, 21, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig narrowScopeWithCondition = { { 807 /* narrowScopeWithCondition */, 3, 22, 3, 25, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig processStmtNodes = { { 931 /* processStmtNodes */, 5, 26, 5, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmtNodesInternal = { { 989 /* processStmtNodesInternal */, 6, 32, 6, 38, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmtNode = { { 1039 /* processStmtNode */, 5, 39, 5, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isAnalysedFile = { { 1064 /* isAnalysedFile */, 1, 45, 1, 46, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isReturningStoredExpressionResults = { { 1079 /* isReturningStoredExpressionResults */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isConsumingStoredExpressionResults = { { 1114 /* isConsumingStoredExpressionResults */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig lookForSetAllowedUndefinedExpressions = { { 1149 /* lookForSetAllowedUndefinedExpressions */, 2, 49, 2, 51, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig lookForUnsetAllowedUndefinedExpressions = { { 1187 /* lookForUnsetAllowedUndefinedExpressions */, 2, 52, 2, 54, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig lookForExpressionCallback = { { 1244 /* lookForExpressionCallback */, 3, 55, 3, 58, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processExprNodeConsumingStored = { { 1305 /* processExprNodeConsumingStored */, 6, 59, 6, 65, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processExprOnDemand = { { 1336 /* processExprOnDemand */, 3, 66, 3, 69, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig readStoredResult = { { 1356 /* readStoredResult */, 2, 70, 2, 72, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig readTypeOfMaybeStored = { { 1391 /* readTypeOfMaybeStored */, 2, 73, 2, 75, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig findScopeStateType = { { 1413 /* findScopeStateType */, 2, 76, 2, 78, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig readScopeStateOrSyntheticType = { { 1432 /* readScopeStateOrSyntheticType */, 2, 79, 2, 81, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig requireScopeStateType = { { 1462 /* requireScopeStateType */, 2, 82, 2, 84, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig guardAgainstUnprocessedRealNode = { { 1491 /* guardAgainstUnprocessedRealNode */, 2, 85, 2, 87, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processSyntheticOnDemand = { { 1523 /* processSyntheticOnDemand */, 2, 88, 2, 90, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processExprNode = { { 1548 /* processExprNode */, 6, 91, 6, 97, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processExprNodeInternal = { { 1564 /* processExprNodeInternal */, 6, 98, 6, 104, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getImpurePointsFromPropertyHook = { { 1709 /* getImpurePointsFromPropertyHook */, 4, 105, 4, 109, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isPropertyHookBackingValueAccess = { { 1741 /* isPropertyHookBackingValueAccess */, 2, 110, 2, 112, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getAssignedVariables = { { 1774 /* getAssignedVariables */, 1, 113, 1, 114, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isReplayableConvergenceBody = { { 1814 /* isReplayableConvergenceBody */, 2, 115, 2, 117, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasContextSensitiveConstruct = { { 1847 /* hasContextSensitiveConstruct */, 1, 118, 1, 119, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig pushNodeGatherer = { { 1885 /* pushNodeGatherer */, 1, 120, 1, 121, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig popNodeGatherer = { { 1902 /* popNodeGatherer */, 0, 122, 0, 122, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig suspendNodeGatherers = { { 1918 /* suspendNodeGatherers */, 0, 123, 0, 123, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig restoreNodeGatherers = { { 1949 /* restoreNodeGatherers */, 1, 124, 1, 125, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig replayRecording = { { 2019 /* replayRecording */, 4, 126, 4, 130, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig replayRecordingRange = { { 2043 /* replayRecordingRange */, 6, 131, 6, 137, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig callNodeCallbackWithExpression = { { 2064 /* callNodeCallbackWithExpression */, 5, 138, 5, 143, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig callNodeCallback = { { 2095 /* callNodeCallback */, 4, 144, 4, 148, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig observingTemplateArgumentFrame = { { 2160 /* observingTemplateArgumentFrame */, 1, 149, 1, 150, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig collectReturnSend = { { 2260 /* collectReturnSend */, 2, 151, 2, 153, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig collectYieldSend = { { 2296 /* collectYieldSend */, 3, 154, 3, 157, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig collectYieldFromSend = { { 2327 /* collectYieldFromSend */, 2, 158, 2, 160, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDeclaredReturnType = { { 2348 /* getDeclaredReturnType */, 1, 161, 1, 162, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig processLoopFinalStmtNodes = { { 998 /* processLoopFinalStmtNodes */, 7, 32, 7, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processLoopPassStmtNodes = { { 1040 /* processLoopPassStmtNodes */, 8, 40, 8, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmtNodesInternal = { { 1065 /* processStmtNodesInternal */, 6, 49, 6, 55, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmtNode = { { 1115 /* processStmtNode */, 5, 56, 5, 61, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isAnalysedFile = { { 1140 /* isAnalysedFile */, 1, 62, 1, 63, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isReturningStoredExpressionResults = { { 1155 /* isReturningStoredExpressionResults */, 0, 64, 0, 64, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isConsumingStoredExpressionResults = { { 1190 /* isConsumingStoredExpressionResults */, 0, 65, 0, 65, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig lookForSetAllowedUndefinedExpressions = { { 1225 /* lookForSetAllowedUndefinedExpressions */, 2, 66, 2, 68, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig lookForUnsetAllowedUndefinedExpressions = { { 1263 /* lookForUnsetAllowedUndefinedExpressions */, 2, 69, 2, 71, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig lookForExpressionCallback = { { 1320 /* lookForExpressionCallback */, 3, 72, 3, 75, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processExprNodeConsumingStored = { { 1381 /* processExprNodeConsumingStored */, 6, 76, 6, 82, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processExprOnDemand = { { 1412 /* processExprOnDemand */, 3, 83, 3, 86, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig readStoredResult = { { 1432 /* readStoredResult */, 2, 87, 2, 89, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig readTypeOfMaybeStored = { { 1467 /* readTypeOfMaybeStored */, 2, 90, 2, 92, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findScopeStateType = { { 1489 /* findScopeStateType */, 2, 93, 2, 95, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig readScopeStateOrSyntheticType = { { 1508 /* readScopeStateOrSyntheticType */, 2, 96, 2, 98, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig requireScopeStateType = { { 1538 /* requireScopeStateType */, 2, 99, 2, 101, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig guardAgainstUnprocessedRealNode = { { 1567 /* guardAgainstUnprocessedRealNode */, 2, 102, 2, 104, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processSyntheticOnDemand = { { 1599 /* processSyntheticOnDemand */, 2, 105, 2, 107, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processExprNode = { { 1624 /* processExprNode */, 6, 108, 6, 114, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processExprNodeInternal = { { 1640 /* processExprNodeInternal */, 6, 115, 6, 121, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getImpurePointsFromPropertyHook = { { 1785 /* getImpurePointsFromPropertyHook */, 4, 122, 4, 126, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isPropertyHookBackingValueAccess = { { 1817 /* isPropertyHookBackingValueAccess */, 2, 127, 2, 129, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getAssignedVariables = { { 1850 /* getAssignedVariables */, 1, 130, 1, 131, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isReplayableConvergenceBody = { { 1890 /* isReplayableConvergenceBody */, 2, 132, 2, 134, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isContextSensitiveStatement = { { 1918 /* isContextSensitiveStatement */, 1, 135, 1, 136, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasContextSensitiveConstruct = { { 1951 /* hasContextSensitiveConstruct */, 1, 137, 1, 138, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig pushNodeGatherer = { { 1989 /* pushNodeGatherer */, 1, 139, 1, 140, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig popNodeGatherer = { { 2006 /* popNodeGatherer */, 0, 141, 0, 141, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig suspendNodeGatherers = { { 2022 /* suspendNodeGatherers */, 0, 142, 0, 142, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig restoreNodeGatherers = { { 2053 /* restoreNodeGatherers */, 1, 143, 1, 144, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig replayRecording = { { 2123 /* replayRecording */, 4, 145, 4, 149, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig replayRecordingRange = { { 2147 /* replayRecordingRange */, 6, 150, 6, 156, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig callNodeCallbackWithExpression = { { 2168 /* callNodeCallbackWithExpression */, 5, 157, 5, 162, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig callNodeCallback = { { 2199 /* callNodeCallback */, 4, 163, 4, 167, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig observingTemplateArgumentFrame = { { 2264 /* observingTemplateArgumentFrame */, 1, 168, 1, 169, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig collectReturnSend = { { 2364 /* collectReturnSend */, 2, 170, 2, 172, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig collectYieldSend = { { 2400 /* collectYieldSend */, 3, 173, 3, 176, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig collectYieldFromSend = { { 2431 /* collectYieldFromSend */, 2, 177, 2, 179, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDeclaredReturnType = { { 2452 /* getDeclaredReturnType */, 1, 180, 1, 181, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::NodeScopeResolver diff --git a/turbo-ext/src/generated/StatementsHandler.h b/turbo-ext/src/generated/StatementsHandler.h index 87d5abad04b..08f4e1b1eed 100644 --- a/turbo-ext/src/generated/StatementsHandler.h +++ b/turbo-ext/src/generated/StatementsHandler.h @@ -83,80 +83,92 @@ inline constexpr char strings[] = "stmts\0" /* 897 */ "PHPStan\\Analyser\\InternalStatementResult\0" /* 903 */ "doProcessStmtNodes\0" /* 944 */ - "i\0" /* 963 */ - "stmt\0" /* 965 */ - "PhpParser\\Node\\Stmt\0" /* 970 */ - "state\0" /* 990 */ - "PHPStan\\Analyser\\StatementListWalkState\0" /* 996 */ - "shouldCheckLastStatement\0" /* 1036 */ - "processStatementStep\0" /* 1061 */ - "PHPStan\\Analyser\\ExpressionResult\0" /* 1082 */ - "readEndStatementExprResult\0" /* 1116 */ - "processBodyStmtNodesTwoPass\0" /* 1143 */ - "staticSites\0" /* 1171 */ - "staticStatementIndexes\0" /* 1183 */ - "parentFrame\0" /* 1206 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1218 */ - "entries\0" /* 1266 */ - "recording\0" /* 1274 */ - "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1284 */ - "inferStaticVariableTypes\0" /* 1323 */ - "staticWalk\0" /* 1348 */ - "frame\0" /* 1359 */ - "parentConstraints\0" /* 1365 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1383 */ - "replayStaticVariableWalk\0" /* 1437 */ - "bodyScope\0" /* 1462 */ - "endState\0" /* 1472 */ - "moreScopes\0" /* 1481 */ - "collectStaticVariableScopes\0" /* 1492 */ - "collectStaticVariableStateScopes\0" /* 1520 */ - "names\0" /* 1553 */ - "scopes\0" /* 1559 */ - "collectStaticVariableTypes\0" /* 1566 */ - "runs\0" /* 1593 */ - "types\0" /* 1598 */ - "walkedConditionalExpressions\0" /* 1604 */ - "enteredTypes\0" /* 1633 */ - "collectStaticVariableConditionalExpressions\0" /* 1646 */ - "isWalkedStaticVariableState\0" /* 1690 */ - "a\0" /* 1718 */ - "b\0" /* 1720 */ - "equalStaticVariableStates\0" /* 1722 */ - "equalStaticVariableConditionalExpressions\0" /* 1748 */ - "PHPStan\\Analyser\\ConditionalExpressionHolder\0" /* 1790 */ - "equalConditionalExpressionHolders\0" /* 1835 */ - "walkTypes\0" /* 1869 */ - "generalize\0" /* 1879 */ - "joinStaticVariableTypes\0" /* 1890 */ - "PHPStan\\Analyser\\ClosureProcessor\0" /* 1914 */ - "getClosureProcessor\0" /* 1948 */ - "processDeferredByRefClosureBodies\0" /* 1968 */ - "statementStartTokenPositions\0" /* 2002 */ - "observeClosureSignatures\0" /* 2031 */ - "recordedEntry\0" /* 2056 */ - "recordedExit\0" /* 2070 */ - "withRecordedConstraints\0" /* 2083 */ - "containsLabels\0" /* 2107 */ - "from\0" /* 2122 */ - "to\0" /* 2127 */ - "appendRecordedStatementResults\0" /* 2130 */ - "PHPStan\\Analyser\\VariableFlow\0" /* 2161 */ - "getVariableMentionFlow\0" /* 2191 */ - "variableNames\0" /* 2214 */ - "statementMentionsAnyVariable\0" /* 2228 */ - "node\0" /* 2257 */ - "mentionsEverything\0" /* 2262 */ - "collectMentionedVariables\0" /* 2281 */ - "getVarTagDependencies\0" /* 2307 */ - "statement\0" /* 2329 */ - "getOverridingThrowPoints\0" /* 2339 */ - "defaultExpr\0" /* 2364 */ - "PhpParser\\Node\\Expr\0" /* 2376 */ - "processStmtVarAnnotation\0" /* 2396 */ - "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 2421 */ - "findSingleVariableLessVarTag\0" /* 2447 */ - "emitVarTagChangedNode"; /* 2476 */ + "previous\0" /* 963 */ + "record\0" /* 972 */ + "processLoopPassStmtNodes\0" /* 979 */ + "lastPass\0" /* 1004 */ + "processLoopFinalStmtNodes\0" /* 1013 */ + "replayTarget\0" /* 1039 */ + "finalWalk\0" /* 1052 */ + "walkAgainstPreviousPass\0" /* 1062 */ + "variableNames\0" /* 1086 */ + "withByRefClosureHolders\0" /* 1100 */ + "state\0" /* 1124 */ + "PHPStan\\Analyser\\StatementListWalkState\0" /* 1130 */ + "exitPointCount\0" /* 1170 */ + "throwPointCount\0" /* 1185 */ + "recordedEntry\0" /* 1201 */ + "rebaseAppendedPoints\0" /* 1215 */ + "i\0" /* 1236 */ + "stmt\0" /* 1238 */ + "PhpParser\\Node\\Stmt\0" /* 1243 */ + "shouldCheckLastStatement\0" /* 1263 */ + "processStatementStep\0" /* 1288 */ + "PHPStan\\Analyser\\ExpressionResult\0" /* 1309 */ + "readEndStatementExprResult\0" /* 1343 */ + "processBodyStmtNodesTwoPass\0" /* 1370 */ + "staticSites\0" /* 1398 */ + "staticStatementIndexes\0" /* 1410 */ + "parentFrame\0" /* 1433 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1445 */ + "entries\0" /* 1493 */ + "recording\0" /* 1501 */ + "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1511 */ + "inferStaticVariableTypes\0" /* 1550 */ + "staticWalk\0" /* 1575 */ + "frame\0" /* 1586 */ + "parentConstraints\0" /* 1592 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1610 */ + "replayStaticVariableWalk\0" /* 1664 */ + "bodyScope\0" /* 1689 */ + "endState\0" /* 1699 */ + "moreScopes\0" /* 1708 */ + "collectStaticVariableScopes\0" /* 1719 */ + "collectStaticVariableStateScopes\0" /* 1747 */ + "names\0" /* 1780 */ + "scopes\0" /* 1786 */ + "collectStaticVariableTypes\0" /* 1793 */ + "runs\0" /* 1820 */ + "types\0" /* 1825 */ + "walkedConditionalExpressions\0" /* 1831 */ + "enteredTypes\0" /* 1860 */ + "collectStaticVariableConditionalExpressions\0" /* 1873 */ + "isWalkedStaticVariableState\0" /* 1917 */ + "a\0" /* 1945 */ + "b\0" /* 1947 */ + "equalStaticVariableStates\0" /* 1949 */ + "equalStaticVariableConditionalExpressions\0" /* 1975 */ + "PHPStan\\Analyser\\ConditionalExpressionHolder\0" /* 2017 */ + "equalConditionalExpressionHolders\0" /* 2062 */ + "walkTypes\0" /* 2096 */ + "generalize\0" /* 2106 */ + "joinStaticVariableTypes\0" /* 2117 */ + "PHPStan\\Analyser\\ClosureProcessor\0" /* 2141 */ + "getClosureProcessor\0" /* 2175 */ + "processDeferredByRefClosureBodies\0" /* 2195 */ + "statementStartTokenPositions\0" /* 2229 */ + "observeClosureSignatures\0" /* 2258 */ + "recordedExit\0" /* 2283 */ + "withRecordedConstraints\0" /* 2296 */ + "containsLabels\0" /* 2320 */ + "from\0" /* 2335 */ + "to\0" /* 2340 */ + "appendRecordedStatementResults\0" /* 2343 */ + "PHPStan\\Analyser\\VariableFlow\0" /* 2374 */ + "getVariableMentionFlow\0" /* 2404 */ + "statementMentionsAnyVariable\0" /* 2427 */ + "node\0" /* 2456 */ + "mentionsEverything\0" /* 2461 */ + "collectMentionedVariables\0" /* 2480 */ + "getVarTagDependencies\0" /* 2506 */ + "statement\0" /* 2528 */ + "getOverridingThrowPoints\0" /* 2538 */ + "defaultExpr\0" /* 2563 */ + "PhpParser\\Node\\Expr\0" /* 2575 */ + "processStmtVarAnnotation\0" /* 2595 */ + "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 2620 */ + "findSingleVariableLessVarTag\0" /* 2646 */ + "emitVarTagChangedNode"; /* 2675 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 15), /* __construct $fileTypeMapper */ reg::packed(43, 0, 68), /* __construct $templateArgumentObserver */ @@ -201,20 +213,58 @@ inline constexpr reg::PackedArg args[] = { reg::packed(518, MAY_BE_CALLABLE), /* doProcessStmtNodes $nodeCallback */ reg::packed(632, 0, 640), /* doProcessStmtNodes $context */ reg::packed(531, 0, 903), /* doProcessStmtNodes return */ + reg::packed(357, 0, 375), /* processLoopPassStmtNodes $nodeScopeResolver */ + reg::packed(588, 0, 599), /* processLoopPassStmtNodes $parentNode */ + reg::packed(897, MAY_BE_ARRAY), /* processLoopPassStmtNodes $stmts */ + reg::packed(416, 0, 422), /* processLoopPassStmtNodes $scope */ + reg::packed(624, 0, 477), /* processLoopPassStmtNodes $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* processLoopPassStmtNodes $nodeCallback */ + reg::packed(632, 0, 640), /* processLoopPassStmtNodes $context */ + reg::packed(963, MAY_BE_NULL | MAY_BE_ARRAY), /* processLoopPassStmtNodes $previous */ + reg::packed(972, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, true, false), /* processLoopPassStmtNodes $record */ + reg::packed(531, 0, 903), /* processLoopPassStmtNodes return */ + reg::packed(357, 0, 375), /* processLoopFinalStmtNodes $nodeScopeResolver */ + reg::packed(588, 0, 599), /* processLoopFinalStmtNodes $parentNode */ + reg::packed(897, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $stmts */ + reg::packed(416, 0, 422), /* processLoopFinalStmtNodes $scope */ + reg::packed(624, 0, 477), /* processLoopFinalStmtNodes $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* processLoopFinalStmtNodes $nodeCallback */ + reg::packed(632, 0, 640), /* processLoopFinalStmtNodes $context */ + reg::packed(1004, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $lastPass */ + reg::packed(531, 0, 903), /* processLoopFinalStmtNodes return */ + reg::packed(357, 0, 375), /* walkAgainstPreviousPass $nodeScopeResolver */ + reg::packed(588, 0, 599), /* walkAgainstPreviousPass $parentNode */ + reg::packed(897, MAY_BE_ARRAY), /* walkAgainstPreviousPass $stmts */ + reg::packed(416, 0, 422), /* walkAgainstPreviousPass $scope */ + reg::packed(624, 0, 477), /* walkAgainstPreviousPass $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* walkAgainstPreviousPass $nodeCallback */ + reg::packed(632, 0, 640), /* walkAgainstPreviousPass $context */ + reg::packed(963, MAY_BE_NULL | MAY_BE_ARRAY), /* walkAgainstPreviousPass $previous */ + reg::packed(1039, MAY_BE_NULL | MAY_BE_CALLABLE), /* walkAgainstPreviousPass $replayTarget */ + reg::packed(1052, MAY_BE_BOOL), /* walkAgainstPreviousPass $finalWalk */ + reg::packed(531, MAY_BE_ARRAY), /* walkAgainstPreviousPass return */ + reg::packed(416, 0, 422), /* withByRefClosureHolders $scope */ + reg::packed(1086, MAY_BE_ARRAY), /* withByRefClosureHolders $variableNames */ + reg::packed(531, MAY_BE_ARRAY), /* withByRefClosureHolders return */ + reg::packed(1124, 0, 1130), /* rebaseAppendedPoints $state */ + reg::packed(1170, MAY_BE_LONG), /* rebaseAppendedPoints $exitPointCount */ + reg::packed(1185, MAY_BE_LONG), /* rebaseAppendedPoints $throwPointCount */ + reg::packed(1201, 0, 422), /* rebaseAppendedPoints $recordedEntry */ + reg::packed(531, MAY_BE_VOID), /* rebaseAppendedPoints return */ reg::packed(357, 0, 375), /* processStatementStep $nodeScopeResolver */ reg::packed(588, 0, 599), /* processStatementStep $parentNode */ reg::packed(897, MAY_BE_ARRAY), /* processStatementStep $stmts */ - reg::packed(963, MAY_BE_LONG), /* processStatementStep $i */ - reg::packed(965, 0, 970), /* processStatementStep $stmt */ - reg::packed(990, 0, 996), /* processStatementStep $state */ + reg::packed(1236, MAY_BE_LONG), /* processStatementStep $i */ + reg::packed(1238, 0, 1243), /* processStatementStep $stmt */ + reg::packed(1124, 0, 1130), /* processStatementStep $state */ reg::packed(624, 0, 477), /* processStatementStep $storage */ reg::packed(518, MAY_BE_CALLABLE), /* processStatementStep $nodeCallback */ reg::packed(632, 0, 640), /* processStatementStep $context */ - reg::packed(1036, MAY_BE_BOOL), /* processStatementStep $shouldCheckLastStatement */ + reg::packed(1263, MAY_BE_BOOL), /* processStatementStep $shouldCheckLastStatement */ reg::packed(531, MAY_BE_VOID), /* processStatementStep return */ - reg::packed(965, 0, 970), /* readEndStatementExprResult $stmt */ + reg::packed(1238, 0, 1243), /* readEndStatementExprResult $stmt */ reg::packed(624, 0, 477), /* readEndStatementExprResult $storage */ - reg::packed(531, MAY_BE_NULL, 1082), /* readEndStatementExprResult return */ + reg::packed(531, MAY_BE_NULL, 1309), /* readEndStatementExprResult return */ reg::packed(357, 0, 375), /* processBodyStmtNodesTwoPass $nodeScopeResolver */ reg::packed(588, 0, 599), /* processBodyStmtNodesTwoPass $parentNode */ reg::packed(897, MAY_BE_ARRAY), /* processBodyStmtNodesTwoPass $stmts */ @@ -226,124 +276,124 @@ inline constexpr reg::PackedArg args[] = { reg::packed(357, 0, 375), /* inferStaticVariableTypes $nodeScopeResolver */ reg::packed(588, 0, 599), /* inferStaticVariableTypes $parentNode */ reg::packed(897, MAY_BE_ARRAY), /* inferStaticVariableTypes $stmts */ - reg::packed(1171, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticSites */ - reg::packed(1183, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticStatementIndexes */ - reg::packed(1206, MAY_BE_NULL, 1218), /* inferStaticVariableTypes $parentFrame */ - reg::packed(1266, MAY_BE_ARRAY), /* inferStaticVariableTypes $entries */ - reg::packed(1274, 0, 1284), /* inferStaticVariableTypes $recording */ + reg::packed(1398, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticSites */ + reg::packed(1410, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticStatementIndexes */ + reg::packed(1433, MAY_BE_NULL, 1445), /* inferStaticVariableTypes $parentFrame */ + reg::packed(1493, MAY_BE_ARRAY), /* inferStaticVariableTypes $entries */ + reg::packed(1501, 0, 1511), /* inferStaticVariableTypes $recording */ reg::packed(624, 0, 477), /* inferStaticVariableTypes $storage */ reg::packed(632, 0, 640), /* inferStaticVariableTypes $context */ reg::packed(531, MAY_BE_ARRAY), /* inferStaticVariableTypes return */ reg::packed(357, 0, 375), /* replayStaticVariableWalk $nodeScopeResolver */ reg::packed(897, MAY_BE_ARRAY), /* replayStaticVariableWalk $stmts */ - reg::packed(1266, MAY_BE_ARRAY), /* replayStaticVariableWalk $entries */ - reg::packed(1274, 0, 1284), /* replayStaticVariableWalk $recording */ - reg::packed(1348, MAY_BE_ARRAY), /* replayStaticVariableWalk $staticWalk */ - reg::packed(1359, 0, 1218), /* replayStaticVariableWalk $frame */ - reg::packed(1206, MAY_BE_NULL, 1218), /* replayStaticVariableWalk $parentFrame */ - reg::packed(1365, MAY_BE_NULL, 1383), /* replayStaticVariableWalk $parentConstraints */ + reg::packed(1493, MAY_BE_ARRAY), /* replayStaticVariableWalk $entries */ + reg::packed(1501, 0, 1511), /* replayStaticVariableWalk $recording */ + reg::packed(1575, MAY_BE_ARRAY), /* replayStaticVariableWalk $staticWalk */ + reg::packed(1586, 0, 1445), /* replayStaticVariableWalk $frame */ + reg::packed(1433, MAY_BE_NULL, 1445), /* replayStaticVariableWalk $parentFrame */ + reg::packed(1592, MAY_BE_NULL, 1610), /* replayStaticVariableWalk $parentConstraints */ reg::packed(624, 0, 477), /* replayStaticVariableWalk $storage */ reg::packed(518, MAY_BE_CALLABLE), /* replayStaticVariableWalk $nodeCallback */ reg::packed(416, 0, 422), /* replayStaticVariableWalk $scope */ reg::packed(531, 0, 903), /* replayStaticVariableWalk return */ - reg::packed(1462, 0, 422), /* collectStaticVariableScopes $bodyScope */ - reg::packed(1274, 0, 1284), /* collectStaticVariableScopes $recording */ - reg::packed(1472, 0, 996), /* collectStaticVariableScopes $endState */ - reg::packed(1481, MAY_BE_ARRAY), /* collectStaticVariableScopes $moreScopes */ + reg::packed(1689, 0, 422), /* collectStaticVariableScopes $bodyScope */ + reg::packed(1501, 0, 1511), /* collectStaticVariableScopes $recording */ + reg::packed(1699, 0, 1130), /* collectStaticVariableScopes $endState */ + reg::packed(1708, MAY_BE_ARRAY), /* collectStaticVariableScopes $moreScopes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableScopes return */ - reg::packed(1462, 0, 422), /* collectStaticVariableStateScopes $bodyScope */ - reg::packed(1274, 0, 1284), /* collectStaticVariableStateScopes $recording */ - reg::packed(1472, 0, 996), /* collectStaticVariableStateScopes $endState */ - reg::packed(1481, MAY_BE_ARRAY), /* collectStaticVariableStateScopes $moreScopes */ + reg::packed(1689, 0, 422), /* collectStaticVariableStateScopes $bodyScope */ + reg::packed(1501, 0, 1511), /* collectStaticVariableStateScopes $recording */ + reg::packed(1699, 0, 1130), /* collectStaticVariableStateScopes $endState */ + reg::packed(1708, MAY_BE_ARRAY), /* collectStaticVariableStateScopes $moreScopes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableStateScopes return */ - reg::packed(1553, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ - reg::packed(1559, MAY_BE_ARRAY), /* collectStaticVariableTypes $scopes */ + reg::packed(1780, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ + reg::packed(1786, MAY_BE_ARRAY), /* collectStaticVariableTypes $scopes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableTypes return */ - reg::packed(1593, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $runs */ - reg::packed(1559, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $scopes */ + reg::packed(1820, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $runs */ + reg::packed(1786, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $scopes */ reg::packed(624, 0, 477), /* collectStaticVariableConditionalExpressions $storage */ - reg::packed(1598, MAY_BE_NULL | MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $types */ - reg::packed(1604, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $walkedConditionalExpressions */ - reg::packed(1633, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $enteredTypes */ + reg::packed(1825, MAY_BE_NULL | MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $types */ + reg::packed(1831, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $walkedConditionalExpressions */ + reg::packed(1860, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $enteredTypes */ reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions return */ - reg::packed(990, MAY_BE_ARRAY), /* isWalkedStaticVariableState $state */ - reg::packed(1598, MAY_BE_ARRAY), /* isWalkedStaticVariableState $types */ + reg::packed(1124, MAY_BE_ARRAY), /* isWalkedStaticVariableState $state */ + reg::packed(1825, MAY_BE_ARRAY), /* isWalkedStaticVariableState $types */ reg::packed(416, 0, 422), /* isWalkedStaticVariableState $scope */ - reg::packed(1604, MAY_BE_ARRAY), /* isWalkedStaticVariableState $walkedConditionalExpressions */ + reg::packed(1831, MAY_BE_ARRAY), /* isWalkedStaticVariableState $walkedConditionalExpressions */ reg::packed(531, MAY_BE_BOOL), /* isWalkedStaticVariableState return */ - reg::packed(1718, MAY_BE_ARRAY), /* equalStaticVariableStates $a */ - reg::packed(1720, MAY_BE_ARRAY), /* equalStaticVariableStates $b */ + reg::packed(1945, MAY_BE_ARRAY), /* equalStaticVariableStates $a */ + reg::packed(1947, MAY_BE_ARRAY), /* equalStaticVariableStates $b */ reg::packed(531, MAY_BE_BOOL), /* equalStaticVariableStates return */ - reg::packed(1718, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $a */ - reg::packed(1720, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $b */ + reg::packed(1945, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $a */ + reg::packed(1947, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $b */ reg::packed(531, MAY_BE_BOOL), /* equalStaticVariableConditionalExpressions return */ - reg::packed(1718, 0, 1790), /* equalConditionalExpressionHolders $a */ - reg::packed(1720, 0, 1790), /* equalConditionalExpressionHolders $b */ + reg::packed(1945, 0, 2017), /* equalConditionalExpressionHolders $a */ + reg::packed(1947, 0, 2017), /* equalConditionalExpressionHolders $b */ reg::packed(531, MAY_BE_BOOL), /* equalConditionalExpressionHolders return */ - reg::packed(1462, 0, 422), /* joinStaticVariableTypes $bodyScope */ - reg::packed(1553, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ - reg::packed(1598, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ - reg::packed(1869, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ - reg::packed(1879, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ + reg::packed(1689, 0, 422), /* joinStaticVariableTypes $bodyScope */ + reg::packed(1780, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ + reg::packed(1825, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ + reg::packed(2096, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ + reg::packed(2106, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ reg::packed(531, MAY_BE_ARRAY), /* joinStaticVariableTypes return */ - reg::packed(531, 0, 1914), /* getClosureProcessor return */ + reg::packed(531, 0, 2141), /* getClosureProcessor return */ reg::packed(357, 0, 375), /* processDeferredByRefClosureBodies $nodeScopeResolver */ - reg::packed(1359, 0, 1218), /* processDeferredByRefClosureBodies $frame */ - reg::packed(990, 0, 996), /* processDeferredByRefClosureBodies $state */ + reg::packed(1586, 0, 1445), /* processDeferredByRefClosureBodies $frame */ + reg::packed(1124, 0, 1130), /* processDeferredByRefClosureBodies $state */ reg::packed(624, 0, 477), /* processDeferredByRefClosureBodies $storage */ reg::packed(518, MAY_BE_CALLABLE), /* processDeferredByRefClosureBodies $nodeCallback */ reg::packed(531, MAY_BE_VOID), /* processDeferredByRefClosureBodies return */ reg::packed(357, 0, 375), /* observeClosureSignatures $nodeScopeResolver */ reg::packed(588, 0, 599), /* observeClosureSignatures $parentNode */ reg::packed(897, MAY_BE_ARRAY), /* observeClosureSignatures $stmts */ - reg::packed(1359, 0, 1218), /* observeClosureSignatures $frame */ - reg::packed(1266, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ + reg::packed(1586, 0, 1445), /* observeClosureSignatures $frame */ + reg::packed(1493, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ reg::packed(624, 0, 477), /* observeClosureSignatures $storage */ reg::packed(632, 0, 640), /* observeClosureSignatures $context */ - reg::packed(2002, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ - reg::packed(531, 0, 1218), /* observeClosureSignatures return */ + reg::packed(2229, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ + reg::packed(531, 0, 1445), /* observeClosureSignatures return */ reg::packed(416, 0, 422), /* withRecordedConstraints $scope */ - reg::packed(2056, 0, 422), /* withRecordedConstraints $recordedEntry */ - reg::packed(2070, 0, 422), /* withRecordedConstraints $recordedExit */ + reg::packed(1201, 0, 422), /* withRecordedConstraints $recordedEntry */ + reg::packed(2283, 0, 422), /* withRecordedConstraints $recordedExit */ reg::packed(531, 0, 422), /* withRecordedConstraints return */ reg::packed(897, MAY_BE_ARRAY), /* containsLabels $stmts */ reg::packed(531, MAY_BE_BOOL), /* containsLabels return */ - reg::packed(990, 0, 996), /* appendRecordedStatementResults $state */ - reg::packed(2122, 0, 996), /* appendRecordedStatementResults $from */ - reg::packed(2127, 0, 996), /* appendRecordedStatementResults $to */ + reg::packed(1124, 0, 1130), /* appendRecordedStatementResults $state */ + reg::packed(2335, 0, 1130), /* appendRecordedStatementResults $from */ + reg::packed(2340, 0, 1130), /* appendRecordedStatementResults $to */ reg::packed(531, MAY_BE_VOID), /* appendRecordedStatementResults return */ - reg::packed(965, 0, 970), /* getVariableMentionFlow $stmt */ - reg::packed(531, MAY_BE_NULL, 2161), /* getVariableMentionFlow return */ - reg::packed(965, 0, 970), /* statementMentionsAnyVariable $stmt */ - reg::packed(2214, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ + reg::packed(1238, 0, 1243), /* getVariableMentionFlow $stmt */ + reg::packed(531, MAY_BE_NULL, 2374), /* getVariableMentionFlow return */ + reg::packed(1238, 0, 1243), /* statementMentionsAnyVariable $stmt */ + reg::packed(1086, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ reg::packed(531, MAY_BE_BOOL), /* statementMentionsAnyVariable return */ - reg::packed(2257, 0, 599), /* collectMentionedVariables $node */ - reg::packed(1553, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ - reg::packed(2262, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ + reg::packed(2456, 0, 599), /* collectMentionedVariables $node */ + reg::packed(1780, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ + reg::packed(2461, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ reg::packed(531, MAY_BE_VOID), /* collectMentionedVariables return */ - reg::packed(965, 0, 970), /* getVarTagDependencies $stmt */ + reg::packed(1238, 0, 1243), /* getVarTagDependencies $stmt */ reg::packed(416, 0, 422), /* getVarTagDependencies $scope */ reg::packed(531, MAY_BE_NULL, 532), /* getVarTagDependencies return */ - reg::packed(2329, 0, 970), /* getOverridingThrowPoints $statement */ + reg::packed(2528, 0, 1243), /* 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(624, 0, 477), /* processStmtVarAnnotation $storage */ - reg::packed(965, 0, 970), /* processStmtVarAnnotation $stmt */ - reg::packed(2364, MAY_BE_NULL, 2376), /* processStmtVarAnnotation $defaultExpr */ + reg::packed(1238, 0, 1243), /* processStmtVarAnnotation $stmt */ + reg::packed(2563, MAY_BE_NULL, 2575), /* 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(965, 0, 970), /* findSingleVariableLessVarTag $stmt */ - reg::packed(531, MAY_BE_NULL, 2421), /* findSingleVariableLessVarTag return */ + reg::packed(1238, 0, 1243), /* findSingleVariableLessVarTag $stmt */ + reg::packed(531, MAY_BE_NULL, 2620), /* findSingleVariableLessVarTag return */ reg::packed(357, 0, 375), /* emitVarTagChangedNode $nodeScopeResolver */ reg::packed(416, 0, 422), /* emitVarTagChangedNode $scope */ reg::packed(624, 0, 477), /* emitVarTagChangedNode $storage */ - reg::packed(965, 0, 970), /* emitVarTagChangedNode $stmt */ - reg::packed(2364, 0, 2376), /* emitVarTagChangedNode $defaultExpr */ + reg::packed(1238, 0, 1243), /* emitVarTagChangedNode $stmt */ + reg::packed(2563, 0, 2575), /* emitVarTagChangedNode $defaultExpr */ reg::packed(518, MAY_BE_CALLABLE), /* emitVarTagChangedNode $nodeCallback */ - reg::packed(531, 0, 1383), /* emitVarTagChangedNode return */ + reg::packed(531, 0, 1610), /* emitVarTagChangedNode return */ }; using Sig = reg::Sig; } // namespace sigtab @@ -357,34 +407,39 @@ inline constexpr sigtab::Sig mergeForwardGotoExitPoints = { { 790 /* mergeForwar inline constexpr sigtab::Sig processUnreachableStatement = { { 827 /* processUnreachableStatement */, 5, 26, 5, 31, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig getNextUnreachableStatements = { { 868 /* getNextUnreachableStatements */, 2, 32, 2, 34, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig doProcessStmtNodes = { { 944 /* doProcessStmtNodes */, 7, 35, 7, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStatementStep = { { 1061 /* processStatementStep */, 10, 43, 10, 53, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig readEndStatementExprResult = { { 1116 /* readEndStatementExprResult */, 2, 54, 2, 56, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1143 /* processBodyStmtNodesTwoPass */, 7, 57, 7, 64, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig inferStaticVariableTypes = { { 1323 /* inferStaticVariableTypes */, 10, 65, 10, 75, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig replayStaticVariableWalk = { { 1437 /* replayStaticVariableWalk */, 11, 76, 11, 87, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableScopes = { { 1492 /* collectStaticVariableScopes */, 4, 88, 4, 92, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableStateScopes = { { 1520 /* collectStaticVariableStateScopes */, 4, 93, 4, 97, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1566 /* collectStaticVariableTypes */, 2, 98, 2, 100, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableConditionalExpressions = { { 1646 /* collectStaticVariableConditionalExpressions */, 6, 101, 6, 107, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isWalkedStaticVariableState = { { 1690 /* isWalkedStaticVariableState */, 4, 108, 4, 112, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalStaticVariableStates = { { 1722 /* equalStaticVariableStates */, 2, 113, 2, 115, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalStaticVariableConditionalExpressions = { { 1748 /* equalStaticVariableConditionalExpressions */, 2, 116, 2, 118, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalConditionalExpressionHolders = { { 1835 /* equalConditionalExpressionHolders */, 2, 119, 2, 121, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig joinStaticVariableTypes = { { 1890 /* joinStaticVariableTypes */, 5, 122, 5, 127, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getClosureProcessor = { { 1948 /* getClosureProcessor */, 0, 128, 0, 128, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 1968 /* processDeferredByRefClosureBodies */, 5, 129, 5, 134, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig observeClosureSignatures = { { 2031 /* observeClosureSignatures */, 8, 135, 8, 143, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig withRecordedConstraints = { { 2083 /* withRecordedConstraints */, 3, 144, 3, 147, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig containsLabels = { { 2107 /* containsLabels */, 1, 148, 1, 149, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig appendRecordedStatementResults = { { 2130 /* appendRecordedStatementResults */, 3, 150, 3, 153, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getVariableMentionFlow = { { 2191 /* getVariableMentionFlow */, 1, 154, 1, 155, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 2228 /* statementMentionsAnyVariable */, 2, 156, 2, 158, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectMentionedVariables = { { 2281 /* collectMentionedVariables */, 3, 159, 3, 162, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getVarTagDependencies = { { 2307 /* getVarTagDependencies */, 2, 163, 2, 165, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getOverridingThrowPoints = { { 2339 /* getOverridingThrowPoints */, 2, 166, 2, 168, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmtVarAnnotation = { { 2396 /* processStmtVarAnnotation */, 6, 169, 6, 175, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 2447 /* findSingleVariableLessVarTag */, 2, 176, 2, 178, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2476 /* emitVarTagChangedNode */, 6, 179, 6, 185, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processLoopPassStmtNodes = { { 979 /* processLoopPassStmtNodes */, 9, 43, 9, 52, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processLoopFinalStmtNodes = { { 1013 /* processLoopFinalStmtNodes */, 8, 53, 8, 61, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig walkAgainstPreviousPass = { { 1062 /* walkAgainstPreviousPass */, 10, 62, 10, 72, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withByRefClosureHolders = { { 1100 /* withByRefClosureHolders */, 2, 73, 2, 75, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig rebaseAppendedPoints = { { 1215 /* rebaseAppendedPoints */, 4, 76, 4, 80, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processStatementStep = { { 1288 /* processStatementStep */, 10, 81, 10, 91, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig readEndStatementExprResult = { { 1343 /* readEndStatementExprResult */, 2, 92, 2, 94, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1370 /* processBodyStmtNodesTwoPass */, 7, 95, 7, 102, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig inferStaticVariableTypes = { { 1550 /* inferStaticVariableTypes */, 10, 103, 10, 113, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig replayStaticVariableWalk = { { 1664 /* replayStaticVariableWalk */, 11, 114, 11, 125, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableScopes = { { 1719 /* collectStaticVariableScopes */, 4, 126, 4, 130, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableStateScopes = { { 1747 /* collectStaticVariableStateScopes */, 4, 131, 4, 135, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1793 /* collectStaticVariableTypes */, 2, 136, 2, 138, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableConditionalExpressions = { { 1873 /* collectStaticVariableConditionalExpressions */, 6, 139, 6, 145, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isWalkedStaticVariableState = { { 1917 /* isWalkedStaticVariableState */, 4, 146, 4, 150, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalStaticVariableStates = { { 1949 /* equalStaticVariableStates */, 2, 151, 2, 153, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalStaticVariableConditionalExpressions = { { 1975 /* equalStaticVariableConditionalExpressions */, 2, 154, 2, 156, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalConditionalExpressionHolders = { { 2062 /* equalConditionalExpressionHolders */, 2, 157, 2, 159, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig joinStaticVariableTypes = { { 2117 /* joinStaticVariableTypes */, 5, 160, 5, 165, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClosureProcessor = { { 2175 /* getClosureProcessor */, 0, 166, 0, 166, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 2195 /* processDeferredByRefClosureBodies */, 5, 167, 5, 172, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig observeClosureSignatures = { { 2258 /* observeClosureSignatures */, 8, 173, 8, 181, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withRecordedConstraints = { { 2296 /* withRecordedConstraints */, 3, 182, 3, 185, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig containsLabels = { { 2320 /* containsLabels */, 1, 186, 1, 187, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig appendRecordedStatementResults = { { 2343 /* appendRecordedStatementResults */, 3, 188, 3, 191, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getVariableMentionFlow = { { 2404 /* getVariableMentionFlow */, 1, 192, 1, 193, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 2427 /* statementMentionsAnyVariable */, 2, 194, 2, 196, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectMentionedVariables = { { 2480 /* collectMentionedVariables */, 3, 197, 3, 200, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getVarTagDependencies = { { 2506 /* getVarTagDependencies */, 2, 201, 2, 203, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getOverridingThrowPoints = { { 2538 /* getOverridingThrowPoints */, 2, 204, 2, 206, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmtVarAnnotation = { { 2595 /* processStmtVarAnnotation */, 6, 207, 6, 213, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 2646 /* findSingleVariableLessVarTag */, 2, 214, 2, 216, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2675 /* emitVarTagChangedNode */, 6, 217, 6, 223, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StatementsHandler diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 1090ddfb29c..2a07789bc6a 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -2277,6 +2277,11 @@ extern zend_class_entry *pt_ce_node_scope_resolver; zv::Val pt_node_scope_resolver_process_expr_node(zval *nodeScopeResolver, zval *stmt, zval *expr, zval *scope, zval *storage, zval *nodeCallback, zval *context); zv::Val pt_node_scope_resolver_process_stmt_node(zval *nodeScopeResolver, zval *stmt, zval *scope, zval *storage, zval *nodeCallback, zval *context); zv::Val pt_node_scope_resolver_process_stmt_nodes_internal(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context); +/* $nodeScopeResolver->processLoopPassStmtNodes(...); previous IS_NULL for + * the first pass, record receives this pass's [entries, recording] */ +zv::Val pt_node_scope_resolver_process_loop_pass_stmt_nodes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zv::Val &record); +zv::Val pt_node_scope_resolver_process_loop_final_stmt_nodes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *lastPass); +[[nodiscard]] bool pt_node_scope_resolver_is_context_sensitive_statement(zval *nodeScopeResolver, zval *stmt, bool &out); zv::Val pt_node_scope_resolver_process_expr_on_demand(zval *nodeScopeResolver, zval *expr, zval *scope, zval *storage); zv::Val pt_node_scope_resolver_process_synthetic_on_demand(zval *nodeScopeResolver, zval *expr, zval *scope); zv::Val pt_node_scope_resolver_find_scope_state_type(zval *nodeScopeResolver, zval *expr, zval *scope); @@ -2303,6 +2308,11 @@ zv::Val pt_statements_handler_process_nodes_with_storage(zval *handler, zval *no * null, UNDEF = pending exception */ zv::Val pt_statements_handler_get_var_tag_dependencies(zval *handler, zval *stmt, zval *scope); zv::Val pt_statements_handler_do_process_stmt_nodes(zval *handler, zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context); +/* $handler->processLoopPassStmtNodes(...): one loop convergence pass re-walking + * only what differs from $previous (IS_NULL for the first pass); record + * receives this pass's [entries, recording]. UNDEF = pending exception */ +zv::Val pt_statements_handler_process_loop_pass_stmt_nodes(zval *handler, zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *previous, zv::Val &record); +zv::Val pt_statements_handler_process_loop_final_stmt_nodes(zval *handler, zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *scope, zval *storage, zval *nodeCallback, zval *context, zval *lastPass); zv::Val pt_statements_handler_process_stmt_var_annotation(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *defaultExpr, zval *nodeCallback); zv::Val pt_statements_handler_get_overriding_throw_points(zval *handler, zval *statement, zval *scope); zv::Val pt_statements_handler_emit_var_tag_changed_node(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *defaultExpr, zval *nodeCallback); diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index 9df729e25f5..aa666f9f629 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -4719,6 +4719,7 @@ public function getComments(): array \PHPStan\Analyser\ExprHandler\BinaryOpHandler::class => 'walk-trace.php', \PHPStan\Analyser\ExprHandler\CoalesceHandler::class => 'walk-trace.php', \PHPStan\Analyser\ExprHandler\Helper\CoalesceCompositionHelper::class => 'walk-trace.php', + \PHPStan\Analyser\ExprHandler\Helper\ForeachEmptyOrObjectNarrowingHelper::class => 'walk-trace.php', \PHPStan\Analyser\StmtHandler\BreakContinueHandler::class => 'walk-trace.php', \PHPStan\Analyser\StmtHandler\LabelHandler::class => 'walk-trace.php', \PHPStan\Analyser\StmtHandler\GotoHandler::class => 'walk-trace.php',