From e430d0e1af7d3897ae898b047b559cd67a6f0282 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 27 Sep 2026 06:16:11 +0200 Subject: [PATCH 1/3] Infer the type of a static variable from what the function body does with it A `static $x` without `@var` was `mixed`. Every call starts where a previous one left the variable, so its type at the `static` statement is the default joined with every type the variable takes in the body: at the end, at every return, throw and yield, and at every call that could run the function again (every node the walk reaches). The two-pass driver collects those types from the observation pass and observes the body again from the first `static` statement - walking only the statements that read a variable whose type changed - until the type at each `static` statement holds everything the variable takes after it, generalized like a loop's variables. The template arguments and closure signatures of the body are resolved from the facts of that last walk, so they observe the converged types. Bodies whose variables can change behind the analysis' back ($$name, extract(), parse_str(), include, eval), generators, and variables typed by `@var`, taken by reference or declared `global` keep `mixed`. StaticVarWithoutTypeRule (playground) is no longer needed. Gated by featureToggles.staticVariablesFromUsages (bleeding edge). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01VpbB99tJfUmeArX74xqvHv --- conf/bleedingEdge.neon | 1 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + issue-bot/playground.neon | 8 - .../Generics/StaticVariableInference.php | 223 ++++++++ .../Generics/TemplateArgumentFrame.php | 38 ++ src/Analyser/StatementsHandler.php | 252 ++++++++- .../StmtHandler/StaticVariableHandler.php | 14 +- .../Playground/StaticVarWithoutTypeRule.php | 81 --- src/TrinaryLogic.php | 2 + ...StaticVariablesFromUsagesToggleOffTest.php | 43 ++ .../data/static-variables-from-usages-off.php | 14 + .../Generics/static-variables-off.neon | 3 + .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-13810.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-1870.php | 4 +- .../Analyser/nsrt/new-in-initializers.php | 2 +- .../nsrt/static-variables-from-usages.php | 138 +++++ ...rictComparisonOfDifferentTypesRuleTest.php | 5 + .../ReturnTypeAfterFinallyRuleTest.php | 6 +- .../StaticVarWithoutTypeRuleTest.php | 34 -- .../data/static-var-without-type.php | 31 -- turbo-ext/src/StatementsHandler.cpp | 437 +++++++++++++++- turbo-ext/src/StaticVariableHandler.cpp | 52 +- turbo-ext/src/StaticVariableInference.cpp | 486 ++++++++++++++++++ turbo-ext/src/TemplateArgumentFrame.cpp | 94 +++- turbo-ext/src/generated/StatementsHandler.h | 473 +++++++++-------- .../src/generated/StaticVariableHandler.h | 61 ++- .../src/generated/StaticVariableInference.h | 86 ++++ .../src/generated/TemplateArgumentFrame.h | 118 +++-- turbo-ext/src/support.h | 10 + turbo-ext/tests/smoke.php | 1 + 32 files changed, 2233 insertions(+), 492 deletions(-) create mode 100644 src/Analyser/Generics/StaticVariableInference.php delete mode 100644 src/Rules/Playground/StaticVarWithoutTypeRule.php create mode 100644 tests/PHPStan/Analyser/Generics/StaticVariablesFromUsagesToggleOffTest.php create mode 100644 tests/PHPStan/Analyser/Generics/data/static-variables-from-usages-off.php create mode 100644 tests/PHPStan/Analyser/Generics/static-variables-off.neon create mode 100644 tests/PHPStan/Analyser/nsrt/static-variables-from-usages.php delete mode 100644 tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php delete mode 100644 tests/PHPStan/Rules/Playground/data/static-var-without-type.php create mode 100644 turbo-ext/src/StaticVariableInference.cpp create mode 100644 turbo-ext/src/generated/StaticVariableInference.h diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 9d891ba3d9d..91201231116 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -29,5 +29,6 @@ parameters: sortWithoutEffect: true unresolvedTemplateArguments: true closureSignaturesFromUsages: true + staticVariablesFromUsages: true unusedVariable: true unusedParameters: true diff --git a/conf/config.neon b/conf/config.neon index 3dfc697b24d..52860b21fe9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -61,6 +61,7 @@ parameters: sortWithoutEffect: false unresolvedTemplateArguments: false closureSignaturesFromUsages: false + staticVariablesFromUsages: false unusedVariable: false unusedParameters: false fileExtensions: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 65d1c4d0cfc..3bae9593991 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -58,6 +58,7 @@ parametersSchema: sortWithoutEffect: bool() unresolvedTemplateArguments: bool() closureSignaturesFromUsages: bool() + staticVariablesFromUsages: bool() unusedVariable: bool() unusedParameters: bool() ]) diff --git a/issue-bot/playground.neon b/issue-bot/playground.neon index 9fc2864ff3e..0fd174c8368 100644 --- a/issue-bot/playground.neon +++ b/issue-bot/playground.neon @@ -6,11 +6,3 @@ rules: - PHPStan\Rules\Playground\NotAnalysedTraitRule - PHPStan\Rules\Playground\NoPhpCodeRule - PHPStan\Rules\Playground\PhpdocCommentRule - -conditionalTags: - PHPStan\Rules\Playground\StaticVarWithoutTypeRule: - phpstan.rules.rule: %checkImplicitMixed% - -services: - - - class: PHPStan\Rules\Playground\StaticVarWithoutTypeRule diff --git a/src/Analyser/Generics/StaticVariableInference.php b/src/Analyser/Generics/StaticVariableInference.php new file mode 100644 index 00000000000..4c898e2c9d7 --- /dev/null +++ b/src/Analyser/Generics/StaticVariableInference.php @@ -0,0 +1,223 @@ + + */ + public function getSites(Node $functionLike, array $stmts): array + { + if (!$this->enabled) { + return []; + } + + /** @var list|null $cached */ + $cached = $functionLike->getAttribute(self::SITES_ATTRIBUTE); + if ($cached !== null) { + return $cached; + } + + $sites = self::scanSites($stmts); + $functionLike->setAttribute(self::SITES_ATTRIBUTE, $sites); + + return $sites; + } + + /** Whether the current walk infers the type of the `static` variable. */ + public function isInferred(MutatingScope $scope, Expr\Variable $var): bool + { + $frame = $scope->getCurrentTemplateArgumentFrame(); + if ($frame === null) { + return false; + } + $body = $frame->getClosureSignatureBody(); + if ($body === null) { + return false; + } + foreach ($this->getSites($body, $frame->getClosureSignatureStmts()) as [$site]) { + if ($site === $var) { + return true; + } + } + + return false; + } + + /** + * The [phpdoc, native] types the driver resolved for the `static` variable, + * null while observing. + * + * @return array{Type, Type}|null + */ + public function getResolvedTypes(MutatingScope $scope, Expr\Variable $var): ?array + { + $frame = $scope->getCurrentTemplateArgumentFrame(); + + return $frame !== null ? $frame->getStaticVariableTypes($var) : null; + } + + /** + * @param Node\Stmt[] $stmts + * @return list + */ + private static function scanSites(array $stmts): array + { + /** @var list $candidates */ + $candidates = []; + $excludedNames = []; + foreach ($stmts as $index => $stmt) { + $stack = [$stmt]; + while (count($stack) > 0) { + $node = array_pop($stack); + if ($node instanceof Node\FunctionLike || $node instanceof Node\Stmt\ClassLike) { + continue; + } + if ( + $node instanceof Expr\Yield_ + || $node instanceof Expr\YieldFrom + || $node instanceof Expr\Include_ + || $node instanceof Expr\Eval_ + ) { + return []; + } + if ($node instanceof Expr\Variable && !is_string($node->name)) { + return []; + } + if ( + $node instanceof Expr\FuncCall + && $node->name instanceof Node\Name + && in_array($node->name->toLowerString(), ['extract', 'parse_str'], true) + ) { + return []; + } + if ($node instanceof Expr\AssignRef) { + self::collectRootNames($node->var, $excludedNames); + self::collectRootNames($node->expr, $excludedNames); + } + if ($node instanceof Node\Stmt\Global_) { + foreach ($node->vars as $var) { + self::collectRootNames($var, $excludedNames); + } + } + if ($node instanceof Node\Stmt\Static_ && !self::hasVarTag($node)) { + foreach ($node->vars as $var) { + $candidates[] = [$var->var, $index]; + } + } + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->$subNodeName; + if ($subNode instanceof Node) { + $stack[] = $subNode; + } elseif (is_array($subNode)) { + foreach ($subNode as $item) { + if (!$item instanceof Node) { + continue; + } + $stack[] = $item; + } + } + } + } + } + + $sites = []; + foreach ($candidates as [$var, $index]) { + if (!is_string($var->name) || isset($excludedNames[$var->name])) { + continue; + } + $sites[] = [$var, $index, $var->name]; + } + + return $sites; + } + + /** + * @param array $names + */ + private static function collectRootNames(Expr $expr, array &$names): void + { + while ( + $expr instanceof Expr\ArrayDimFetch + || $expr instanceof Expr\PropertyFetch + || $expr instanceof Expr\NullsafePropertyFetch + || $expr instanceof Expr\StaticPropertyFetch + ) { + if ($expr instanceof Expr\StaticPropertyFetch) { + return; + } + $expr = $expr->var; + } + if ($expr instanceof Expr\Variable && is_string($expr->name)) { + $names[$expr->name] = true; + return; + } + if (!$expr instanceof Expr\List_ && !$expr instanceof Expr\Array_) { + return; + } + + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + self::collectRootNames($item->value, $names); + } + } + + private static function hasVarTag(Node\Stmt\Static_ $stmt): bool + { + $docComment = $stmt->getDocComment(); + + return $docComment !== null && preg_match('~@(?:phpstan-|psalm-)?var\s~', $docComment->getText()) === 1; + } + +} diff --git a/src/Analyser/Generics/TemplateArgumentFrame.php b/src/Analyser/Generics/TemplateArgumentFrame.php index 5a3e44c2c01..6bdcf2cccb7 100644 --- a/src/Analyser/Generics/TemplateArgumentFrame.php +++ b/src/Analyser/Generics/TemplateArgumentFrame.php @@ -65,6 +65,7 @@ public static function returnTypeOfCall(ParametersAcceptor $acceptor, MutatingSc * @param array $settledClosureSites spl_object_id() of the closure nodes * @param bool $observingClosures the template arguments are resolved, the closure signatures observed again * @param array $byRefSites spl_object_id() of the closure node => the node, its statement index, whether every invocation was seen + * @param array $staticVariableTypes spl_object_id() of a `static` variable node => the node, its phpdoc and native type - see StaticVariableInference */ public function __construct( private readonly ?self $parent, @@ -75,10 +76,46 @@ public function __construct( private readonly array $settledClosureSites = [], private readonly bool $observingClosures = false, private readonly array $byRefSites = [], + private readonly array $staticVariableTypes = [], ) { } + /** + * @return array{Type, Type}|null + */ + public function getStaticVariableTypes(Expr\Variable $var): ?array + { + $types = $this->staticVariableTypes[spl_object_id($var)] ?? null; + if ($types === null || $types[0] !== $var) { + return null; + } + + return [$types[1], $types[2]]; + } + + /** + * The frame with the inferred types of the body's `static` variables; the + * statements holding them are walked again in the second pass. + * + * @param array $staticVariableTypes + * @param array $statementIndexes + */ + public function withStaticVariableTypes(array $staticVariableTypes, array $statementIndexes): self + { + return new self( + $this->parent, + $this->resolutions, + $this->siteStatementIndexes + $statementIndexes, + $this->closureSignatureBody, + $this->closureSignatureStmts, + $this->settledClosureSites, + $this->observingClosures, + $this->byRefSites, + $staticVariableTypes, + ); + } + /** * How the second pass treats the by-ref uses of a closure created in this * frame's body - see ClosureSignatureInference::getByRefSiteMode(). @@ -133,6 +170,7 @@ public function withObservedClosures(array $closureResolutions, array $closureSi $this->closureSignatureStmts, $settledClosureSites, byRefSites: $byRefSites, + staticVariableTypes: $this->staticVariableTypes, ); } diff --git a/src/Analyser/StatementsHandler.php b/src/Analyser/StatementsHandler.php index 2489195d8e3..8ca6ecd625d 100644 --- a/src/Analyser/StatementsHandler.php +++ b/src/Analyser/StatementsHandler.php @@ -12,6 +12,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt\Goto_; use PHPStan\Analyser\Generics\ClosureSignatureInference; +use PHPStan\Analyser\Generics\StaticVariableInference; use PHPStan\Analyser\Generics\TemplateArgumentConstraints; use PHPStan\Analyser\Generics\TemplateArgumentFrame; use PHPStan\Analyser\Generics\TemplateArgumentObserver; @@ -31,6 +32,10 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\MixedType; +use PHPStan\Type\NeverType; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; +use function array_fill_keys; use function array_key_exists; use function array_keys; use function array_merge; @@ -42,6 +47,7 @@ use function is_array; use function is_int; use function is_string; +use function min; use function spl_object_id; use function sprintf; @@ -66,6 +72,7 @@ public function __construct( private TemplateArgumentObserver $templateArgumentObserver, private TemplateArgumentResolver $templateArgumentResolver, private Container $container, + private StaticVariableInference $staticVariableInference, #[AutowiredParameter(ref: '%featureToggles.unresolvedTemplateArguments%')] private bool $unresolvedTemplateArguments, ) @@ -568,9 +575,22 @@ private function processBodyStmtNodesTwoPass( } finally { $nodeScopeResolver->restoreNodeGatherers($suspendedGatherers); } - $frame = $this->templateArgumentResolver->resolve($state->scope->getTemplateArgumentConstraints() ?? TemplateArgumentConstraints::createEmpty(), $parentFrame, $statementStartTokenPositions, $parentNode, $stmts); $stmtCount = count($stmts); $entries[$stmtCount] = [clone $state, $recording->count()]; + $constraints = $state->scope->getTemplateArgumentConstraints() ?? TemplateArgumentConstraints::createEmpty(); + $staticSites = $this->staticVariableInference->getSites($parentNode, $stmts); + $staticVariableTypes = []; + $staticStatementIndexes = []; + if ($staticSites !== []) { + foreach ($staticSites as [, $index]) { + $staticStatementIndexes[$index] = true; + } + [$staticVariableTypes, $constraints] = $this->inferStaticVariableTypes($nodeScopeResolver, $parentNode, $stmts, $staticSites, $staticStatementIndexes, $parentFrame, $entries, $recording, $storage, $observationContext); + } + $frame = $this->templateArgumentResolver->resolve($constraints, $parentFrame, $statementStartTokenPositions, $parentNode, $stmts); + if ($staticVariableTypes !== []) { + $frame = $frame->withStaticVariableTypes($staticVariableTypes, $staticStatementIndexes); + } if ($frame->isObservingClosures()) { $frame = $this->observeClosureSignatures($nodeScopeResolver, $parentNode, $stmts, $frame, $entries, $storage, $observationContext, $statementStartTokenPositions); } @@ -654,6 +674,236 @@ private function processBodyStmtNodesTwoPass( return $state->toResult(); } + /** + * The types the body's `static` variables take (see + * StaticVariableInference): the observation pass walked the body with their + * defaults; from the first `static` statement on, the body is observed again + * with the types collected so far - walking, like the second pass, only the + * statements that read a variable whose type changed - until the type at + * each `static` statement holds every type the variable takes after it, + * generalized like a loop's variables. The facts of the last walk are the + * ones the template arguments and closure signatures are resolved from. + * + * @param Node\Stmt[] $stmts + * @param non-empty-list $staticSites + * @param array $staticStatementIndexes + * @param array $entries + * @return array{array, TemplateArgumentConstraints} + */ + private function inferStaticVariableTypes( + NodeScopeResolver $nodeScopeResolver, + Node $parentNode, + array $stmts, + array $staticSites, + array $staticStatementIndexes, + ?TemplateArgumentFrame $parentFrame, + array $entries, + RecordingNodeCallback $recording, + ExpressionResultStorage $storage, + StatementContext $context, + ): array + { + $stmtCount = count($stmts); + $bodyScope = $entries[0][0]->scope; + $names = []; + $start = $stmtCount; + foreach ($staticSites as [, $index, $name]) { + $names[$name] = true; + $start = min($start, $index); + } + $names = array_keys($names); + $types = $this->collectStaticVariableTypes($bodyScope, $names, $recording, $entries[$stmtCount][0], []); + $hasLabels = $this->containsLabels($stmts); + $count = 0; + while (true) { + $siteTypes = []; + foreach ($staticSites as [$var, , $name]) { + [$type, $nativeType] = $types[$name]; + $siteTypes[spl_object_id($var)] = [$var, $type, $nativeType]; + } + $frame = (new TemplateArgumentFrame($parentFrame, closureSignatureBody: $parentNode, closureSignatureStmts: $stmts))->withStaticVariableTypes($siteTypes, $staticStatementIndexes); + + $state = clone $entries[$start][0]; + $state->scope = $state->scope->withTemplateArgumentFrame($frame); + $walkStorage = $storage->duplicate(); + $walkRecording = new RecordingNodeCallback(); + /** @var list $replayedScopes */ + $replayedScopes = []; + $suspendedGatherers = $nodeScopeResolver->suspendNodeGatherers(); + $pushedScope = $state->scope; + $pushedScope->pushExpressionResultStorage($walkStorage); + try { + for ($i = $start; $i < $stmtCount; $i++) { + $recordedEntry = $entries[$i][0]; + $differingRoots = $state->alreadyTerminated === $recordedEntry->alreadyTerminated + ? $state->scope->getDifferingVariableRoots($recordedEntry->scope) + : null; + if ($differingRoots === [] && !$frame->hasSiteAtOrAfter($i)) { + // converged with the observation pass: the rest of it stands + $state->scope = $this->withRecordedConstraints($state->scope, $recordedEntry->scope, $entries[$stmtCount][0]->scope); + $this->appendRecordedStatementResults($state, $recordedEntry, $entries[$stmtCount][0]); + break; + } + + $stmt = $stmts[$i]; + if ( + $differingRoots === null + || $hasLabels + || $frame->ownsSiteInStatement($i) + || $this->statementMentionsAnyVariable($stmt, $differingRoots) + ) { + $this->processStatementStep($nodeScopeResolver, $parentNode, $stmts, $i, $stmt, $state, $walkStorage, $walkRecording, $context, true); + $replayedScopes[] = $state->scope; + continue; + } + + // the statement does not read what changed: the variables keep + // their types through it + $replayedScopes[] = $state->scope; + $recordedExit = $entries[$i + 1][0]; + $this->appendRecordedStatementResults($state, $recordedEntry, $recordedExit); + $state->scope = $this->withRecordedConstraints( + $state->scope->withRecordedStatementDelta($recordedEntry->scope, $recordedExit->scope), + $recordedEntry->scope, + $recordedExit->scope, + ); + } + } finally { + $pushedScope->popExpressionResultStorage(); + $nodeScopeResolver->restoreNodeGatherers($suspendedGatherers); + } + $constraints = $state->scope->getTemplateArgumentConstraints() ?? TemplateArgumentConstraints::createEmpty(); + + $walkTypes = $this->collectStaticVariableTypes($bodyScope, $names, $walkRecording, $state, $replayedScopes); + $converged = true; + foreach ($names as $name) { + if ( + $types[$name][0]->isSuperTypeOf($walkTypes[$name][0])->yes() + && $types[$name][1]->isSuperTypeOf($walkTypes[$name][1])->yes() + ) { + continue; + } + $converged = false; + break; + } + $count++; + if ($converged || $count >= NodeScopeResolver::LOOP_SCOPE_ITERATIONS) { + break; + } + + $types = $this->joinStaticVariableTypes($bodyScope, $names, $types, $walkTypes, $count > NodeScopeResolver::GENERALIZE_AFTER_ITERATION); + } + + $siteTypes = []; + foreach ($staticSites as [$var, , $name]) { + [$type, $nativeType] = $types[$name]; + $siteTypes[spl_object_id($var)] = [$var, $type, $nativeType]; + } + + return [$siteTypes, $constraints]; + } + + /** + * The [phpdoc, native] types the variables take in the scopes the walk + * recorded in the body (not in the function-likes nested in it), at its + * end, returns and throws, and in $moreScopes. + * + * @param list $names + * @param list $moreScopes + * @return array + */ + private function collectStaticVariableTypes(MutatingScope $bodyScope, array $names, RecordingNodeCallback $recording, StatementListWalkState $endState, array $moreScopes): array + { + $scopes = $moreScopes; + $scopes[] = $endState->scope; + foreach ($endState->exitPoints as $exitPoint) { + $scopes[] = $exitPoint->getScope(); + } + foreach ($endState->throwPoints as $throwPoint) { + $scopes[] = $throwPoint->getScope(); + } + foreach ($recording->getPairs() as [, $scope]) { + $scopes[] = $scope; + } + + $bodyFunction = $bodyScope->getFunction(); + $bodyReflection = $bodyScope->getAnonymousFunctionReflection(); + $typesByName = []; + foreach ($names as $name) { + $typesByName[$name] = [[], []]; + } + $seen = []; + foreach ($scopes as $scope) { + if (!$scope instanceof MutatingScope) { + continue; + } + $id = spl_object_id($scope); + if (isset($seen[$id])) { + continue; + } + $seen[$id] = true; + if ($scope->getAnonymousFunctionReflection() !== $bodyReflection || $scope->getFunction() !== $bodyFunction) { + continue; + } + foreach ($names as $name) { + if ($scope->hasVariableType($name)->no()) { + continue; + } + $typesByName[$name][0][] = $scope->getVariableType($name); + $typesByName[$name][1][] = $scope->doNotTreatPhpDocTypesAsCertain()->getVariableType($name); + } + } + + $types = []; + foreach ($typesByName as $name => [$phpDocTypes, $nativeTypes]) { + $types[$name] = $phpDocTypes === [] + ? [new NeverType(), new NeverType()] + : [TypeCombinator::union(...$phpDocTypes), TypeCombinator::union(...$nativeTypes)]; + } + + return $types; + } + + /** + * $types joined with $walkTypes - generalized like a loop's variables once + * the join keeps growing. + * + * @param list $names + * @param array $types + * @param array $walkTypes + * @return array + */ + private function joinStaticVariableTypes(MutatingScope $bodyScope, array $names, array $types, array $walkTypes, bool $generalize): array + { + $joined = []; + foreach ($names as $name) { + $joined[$name] = [ + TypeCombinator::union($types[$name][0], $walkTypes[$name][0]), + TypeCombinator::union($types[$name][1], $walkTypes[$name][1]), + ]; + } + if (!$generalize) { + return $joined; + } + + $previousScope = $bodyScope; + $joinedScope = $bodyScope; + foreach ($names as $name) { + $previousScope = $previousScope->assignVariable($name, $types[$name][0], $types[$name][1], TrinaryLogic::createYes()); + $joinedScope = $joinedScope->assignVariable($name, $joined[$name][0], $joined[$name][1], TrinaryLogic::createYes()); + } + $generalizedScope = $previousScope->generalizeWith($joinedScope, array_fill_keys($names, true)); + $generalized = []; + foreach ($names as $name) { + $generalized[$name] = [ + $generalizedScope->getVariableType($name), + $generalizedScope->doNotTreatPhpDocTypesAsCertain()->getVariableType($name), + ]; + } + + return $generalized; + } + /** * Looked up instead of injected: the closure processor walks closure bodies * through the statements handler, which constructor injection cannot express. diff --git a/src/Analyser/StmtHandler/StaticVariableHandler.php b/src/Analyser/StmtHandler/StaticVariableHandler.php index afc2b3ad5a2..68701ad6148 100644 --- a/src/Analyser/StmtHandler/StaticVariableHandler.php +++ b/src/Analyser/StmtHandler/StaticVariableHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt\Static_; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\Generics\StaticVariableInference; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalStatementResult; use PHPStan\Analyser\MutatingScope; @@ -19,6 +20,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\MixedType; +use PHPStan\Type\NullType; use function array_merge; use function is_string; @@ -32,6 +34,7 @@ final class StaticVariableHandler implements StmtHandler public function __construct( private VarAnnotationProcessor $varAnnotationProcessor, + private StaticVariableInference $staticVariableInference, ) { } @@ -67,6 +70,7 @@ public function processStmt( throw new ShouldNotHappenException(); } + $defaultExprResult = null; if ($var->default !== null) { $defaultExprResult = $nodeScopeResolver->processExprNode($stmt, $var->default, $scope, $storage, $nodeCallback, ExpressionContext::createDeep($context->shouldResolveTemplateArguments())); $variableFlows[] = $defaultExprResult->getVariableFlow(); @@ -79,7 +83,15 @@ public function processStmt( $impurePoints = array_merge($impurePoints, $varResult->getImpurePoints()); $scope = $scope->exitExpressionAssign($var->var); - $scope = $scope->assignVariable($var->var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes()); + // the type the previous calls may have left - see StaticVariableInference + $types = $this->staticVariableInference->getResolvedTypes($scope, $var->var); + if ($types === null && $this->staticVariableInference->isInferred($scope, $var->var)) { + $types = $defaultExprResult !== null + ? [$defaultExprResult->getType(), $defaultExprResult->getNativeType()] + : [new NullType(), new NullType()]; + } + [$type, $nativeType] = $types ?? [new MixedType(), new MixedType()]; + $scope = $scope->assignVariable($var->var->name, $type, $nativeType, TrinaryLogic::createYes()); $vars[] = $var->var->name; } diff --git a/src/Rules/Playground/StaticVarWithoutTypeRule.php b/src/Rules/Playground/StaticVarWithoutTypeRule.php deleted file mode 100644 index 28f53699525..00000000000 --- a/src/Rules/Playground/StaticVarWithoutTypeRule.php +++ /dev/null @@ -1,81 +0,0 @@ - - */ -final class StaticVarWithoutTypeRule implements Rule -{ - - public function __construct( - private FileTypeMapper $fileTypeMapper, - ) - { - } - - public function getNodeType(): string - { - return Node\Stmt\Static_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $docComment = $node->getDocComment(); - $ruleError = RuleErrorBuilder::message('Static variable needs to be typed with PHPDoc @var tag.') - ->identifier('phpstanPlayground.staticWithoutType') - ->build(); - if ($docComment === null) { - return [$ruleError]; - } - $variableNames = []; - foreach ($node->vars as $var) { - if (!is_string($var->var->name)) { - throw new ShouldNotHappenException(); - } - - $variableNames[] = $var->var->name; - } - - $function = $scope->getFunction(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $scope->isInClass() ? $scope->getClassReflection()->getName() : null, - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $function !== null ? $function->getName() : null, - $docComment->getText(), - ); - $varTags = []; - foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { - $varTags[$key] = $varTag; - } - - if (count($varTags) === 0) { - return [$ruleError]; - } - - if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { - return []; - } - - foreach ($variableNames as $variableName) { - if (isset($varTags[$variableName])) { - continue; - } - - return [$ruleError]; - } - - return []; - } - -} diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index 5b997e1d3c0..1707443fdaf 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -54,6 +54,7 @@ final class TrinaryLogic private static self $NO; + /** @param self::YES|self::MAYBE|self::NO $value */ private function __construct(private int $value) { } @@ -79,6 +80,7 @@ public static function createFromBoolean(bool $value): self return self::$registry[$yesNo] ??= new self($yesNo); } + /** @param self::YES|self::MAYBE|self::NO $value */ private static function create(int $value): self { return self::$registry[$value] ??= new self($value); diff --git a/tests/PHPStan/Analyser/Generics/StaticVariablesFromUsagesToggleOffTest.php b/tests/PHPStan/Analyser/Generics/StaticVariablesFromUsagesToggleOffTest.php new file mode 100644 index 00000000000..6df98062deb --- /dev/null +++ b/tests/PHPStan/Analyser/Generics/StaticVariablesFromUsagesToggleOffTest.php @@ -0,0 +1,43 @@ + */ + public static function dataFileAsserts(): iterable + { + yield from self::gatherAssertTypes(__DIR__ . '/data/static-variables-from-usages-off.php'); + } + + /** + * @param mixed ...$args + */ + #[DataProvider('dataFileAsserts')] + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return array_merge(parent::getAdditionalConfigFiles(), [ + __DIR__ . '/../../../../conf/bleedingEdge.neon', + __DIR__ . '/static-variables-off.neon', + ]); + } + +} diff --git a/tests/PHPStan/Analyser/Generics/data/static-variables-from-usages-off.php b/tests/PHPStan/Analyser/Generics/data/static-variables-from-usages-off.php new file mode 100644 index 00000000000..437476ba12b --- /dev/null +++ b/tests/PHPStan/Analyser/Generics/data/static-variables-from-usages-off.php @@ -0,0 +1,14 @@ +assertTrue($scope->hasVariableType('val')->yes()); $this->assertSame('SomeNodeScopeResolverNamespace\InvalidArgumentException', $scope->getVariableType('exception')->describe(VerbosityLevel::precise())); $this->assertTrue($scope->hasVariableType('staticVariable')->yes()); - $this->assertSame($scope->getVariableType('staticVariable')->describe(VerbosityLevel::precise()), 'mixed'); + $this->assertSame($scope->getVariableType('staticVariable')->describe(VerbosityLevel::precise()), 'array{}'); $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType')->yes()); $this->assertSame($scope->getVariableType('staticVariableWithPhpDocType')->describe(VerbosityLevel::precise()), 'string'); $this->assertTrue($scope->hasVariableType('staticVariableWithPhpDocType2')->yes()); diff --git a/tests/PHPStan/Analyser/nsrt/bug-13810.php b/tests/PHPStan/Analyser/nsrt/bug-13810.php index c1377cba0e4..9c8d00afa47 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13810.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13810.php @@ -9,13 +9,13 @@ function doFoo(): void { static $isSupported; - assertType('mixed', $isSupported); + assertType('(Closure(mixed): bool)|null', $isSupported); $isSupported ??= function (mixed $arg) use (&$isSupported): bool { assertType('Closure(mixed): bool', $isSupported); return $isSupported($arg); }; - assertType('mixed~null', $isSupported); + assertType('Closure(mixed): bool', $isSupported); $isSupported('foo'); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-1870.php b/tests/PHPStan/Analyser/nsrt/bug-1870.php index 2010b6cfc11..0dfddae06de 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-1870.php +++ b/tests/PHPStan/Analyser/nsrt/bug-1870.php @@ -11,13 +11,13 @@ public function doFoo(): void { static $i = 0; $i++; - assertType('(float|int)', $i); + assertType('int<1, max>', $i); } public function doBar(): void { static $i = 0; - assertType('(float|int)', ++$i); + assertType('int<1, max>', ++$i); } } diff --git a/tests/PHPStan/Analyser/nsrt/new-in-initializers.php b/tests/PHPStan/Analyser/nsrt/new-in-initializers.php index 091e0ee6283..5ce9c53a814 100644 --- a/tests/PHPStan/Analyser/nsrt/new-in-initializers.php +++ b/tests/PHPStan/Analyser/nsrt/new-in-initializers.php @@ -40,7 +40,7 @@ public function doBar() public function doBaz() { static $o = new \stdClass(); - assertType('mixed', $o); + assertType('stdClass', $o); } } diff --git a/tests/PHPStan/Analyser/nsrt/static-variables-from-usages.php b/tests/PHPStan/Analyser/nsrt/static-variables-from-usages.php new file mode 100644 index 00000000000..d2708e8e3c4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/static-variables-from-usages.php @@ -0,0 +1,138 @@ += 8.0 + +namespace StaticVariablesFromUsages; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class Foo +{ + + public function counter(): int + { + static $count = 0; + assertType('int<0, max>', $count); + $count++; + assertType('int<1, max>', $count); + + return $count; + } + + public function singleton(): self + { + static $instance = null; + assertType('StaticVariablesFromUsages\\Foo|null', $instance); + if ($instance === null) { + $instance = new self(); + } + + return $instance; + } + + public function memoize(string $key): int + { + static $cache = []; + assertType('array>', $cache); + if (isset($cache[$key])) { + return $cache[$key]; + } + + $cache[$key] = strlen($key); + + return $cache[$key]; + } + + public function withoutDefault(): void + { + static $x; + assertType("'a'|null", $x); + $x = 'a'; + } + + public function neverWritten(): void + { + static $x = 5; + assertType('5', $x); + assertNativeType('5', $x); + } + + public function withVarTag(): void + { + /** @var list $x */ + static $x = []; + assertType('list', $x); + $x[] = 'a'; + } + + public function writtenInLoop(array $items): void + { + static $seen = false; + assertType('bool', $seen); + foreach ($items as $item) { + $seen = true; + } + } + + /** @param list $items */ + public function staticInLoop(array $items): void + { + foreach ($items as $item) { + static $last = null; + assertType('int|null', $last); + $last = $item; + } + } + + public function throwsInBetween(): void + { + static $state = 'idle'; + assertType("'idle'|'running'", $state); + $state = 'running'; + $this->mayThrow(); + $state = 'idle'; + } + + public function writtenInClosure(): void + { + static $calls = 0; + assertType('int<0, max>', $calls); + $increment = function () use (&$calls): void { + $calls++; + }; + $increment(); + } + + public function referencedAway(): void + { + static $x = 1; + assertType('mixed', $x); + $y = &$x; + $y = 'a'; + } + + public function generator(): \Generator + { + static $x = 1; + assertType('mixed', $x); + $x = 2; + yield 1; + } + + private function mayThrow(): void + { + } + +} + +function recursion(int $depth): int +{ + static $level = 0; + assertType('int<0, max>', $level); + $level++; + if ($depth > 0) { + recursion($depth - 1); + } + $level--; + + return $level; +} diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index d0e73ab92a1..36770adb30a 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -195,6 +195,11 @@ public function testStrictComparison(): void 'Strict comparison using === between (int|int<2, max>|string) and stdClass will always evaluate to false.', 466, ], + [ + 'Strict comparison using === between int<0, 1>|null and 5 will always evaluate to false.', + 563, + $tipText, + ], [ 'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.', 622, diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeAfterFinallyRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeAfterFinallyRuleTest.php index 0467d7dccc6..dcc53ba6d2a 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeAfterFinallyRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeAfterFinallyRuleTest.php @@ -40,10 +40,8 @@ public function testRule(): void 'Function ReturnTypeAfterFinally\\byRefIncrementedInFinally() should return int but returns string because the finally block modifies the value returned by reference.', 49, ], - [ - 'Function ReturnTypeAfterFinally\\byRefStaticVariable() should return int but returns string because the finally block modifies the value returned by reference.', - 59, - ], + // byRefStaticVariable(): a previous call leaves 'test' in the static + // variable, so ReturnTypeRule reports the return itself [ 'Function ReturnTypeAfterFinally\\byRefPhpDocReturnType() should return int<1, max> but returns -5 because the finally block modifies the value returned by reference.', 70, diff --git a/tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php b/tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php deleted file mode 100644 index 4ef63348287..00000000000 --- a/tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - */ -class StaticVarWithoutTypeRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new StaticVarWithoutTypeRule(self::getContainer()->getByType(FileTypeMapper::class)); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/static-var-without-type.php'], [ - [ - 'Static variable needs to be typed with PHPDoc @var tag.', - 23, - ], - [ - 'Static variable needs to be typed with PHPDoc @var tag.', - 28, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Playground/data/static-var-without-type.php b/tests/PHPStan/Rules/Playground/data/static-var-without-type.php deleted file mode 100644 index 10455d131de..00000000000 --- a/tests/PHPStan/Rules/Playground/data/static-var-without-type.php +++ /dev/null @@ -1,31 +0,0 @@ - 0) { + for (auto entry : zv::ArrRef(staticSites.raw())) { + zval *index = zend_hash_index_find(Z_ARRVAL_P(entry.value().deref().raw()), 1); + if (UNEXPECTED(index == NULL)) continue; + staticStatementIndexes.separate(); + zval marked; + ZVAL_TRUE(&marked); + zend_hash_index_update(staticStatementIndexes.table(), (zend_ulong) zval_get_long(index), &marked); + } + zv::Val inferred = inferStaticVariableTypes(nodeScopeResolver, parentNode, stmts, staticSites.raw(), staticStatementIndexes.raw(), parentFrame.raw(), entries.raw(), recording.raw(), storage, observationContext.raw()); + if (UNEXPECTED(inferred.isUndef())) return zv::Val(); + staticVariableTypes = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(inferred.raw()), 0))); + finalConstraints = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(inferred.raw()), 1))); + } zv::Val frame = templateArgumentResolverResolve(OBJ_PROP_NUM(self, slots::templateArgumentResolver), finalConstraints.raw(), parentFrame.raw(), statementStartTokenPositions.raw(), parentNode, stmts); if (UNEXPECTED(frame.isUndef())) return zv::Val(); - { - zv::Val snapshot = snapshotEntry(state.raw(), recording.raw()); - if (UNEXPECTED(snapshot.isUndef())) return zv::Val(); - entries.separate(); - zend_hash_index_update(entries.table(), (zend_ulong) stmtCount, snapshot.raw()); - ZVAL_UNDEF(snapshot.raw()); + if (zend_hash_num_elements(Z_ARRVAL_P(staticVariableTypes.raw())) > 0) { + frame = pt_template_argument_frame_with_static_variable_types(frame.raw(), staticVariableTypes.raw(), staticStatementIndexes.raw()); + if (UNEXPECTED(frame.isUndef())) return zv::Val(); } { bool observingClosures; @@ -1566,6 +1589,399 @@ class StatementsHandler return true; } + /* the private inferStaticVariableTypes(): [the site types, the + * constraints]; UNDEF = pending exception */ + zv::Val inferStaticVariableTypes(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *staticSites, zval *staticStatementIndexes, zval *parentFrame, zval *entries, zval *recording, zval *storageArg, zval *context) + { + HashTable *stmtsTable = Z_ARRVAL_P(stmts); + zend_long stmtCount = zend_hash_num_elements(stmtsTable); + zval *firstEntry = entryAt(entries, 0); + zval *finalEntry = entryAt(entries, stmtCount); + if (UNEXPECTED(firstEntry == NULL || finalEntry == NULL)) return zv::Val(); + zv::Val bodyScope = zv::Val::copyOf(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(entryState(firstEntry)), stateSlots::scope))); + zv::Arr names = zv::Arr::empty(); + zv::ScratchTable seenNames(8); + zend_long start = stmtCount; + for (auto entry : zv::ArrRef(staticSites)) { + HashTable *site = Z_ARRVAL_P(entry.value().deref().raw()); + zval *index = zend_hash_index_find(site, 1); + zval *name = zend_hash_index_find(site, 2); + if (UNEXPECTED(index == NULL || name == NULL || Z_TYPE_P(name) != IS_STRING)) continue; + if (zval_get_long(index) < start) start = zval_get_long(index); + if (zend_hash_exists(seenNames.table(), Z_STR_P(name))) continue; + zval marked; + ZVAL_TRUE(&marked); + zend_hash_add(seenNames.table(), Z_STR_P(name), &marked); + names.push(zv::Ref(name)); + } + zval emptyArray; + ZVAL_EMPTY_ARRAY(&emptyArray); + zv::Val types = collectStaticVariableTypes(bodyScope.raw(), names.raw(), recording, entryState(finalEntry), &emptyArray); + if (UNEXPECTED(types.isUndef())) return zv::Val(); + bool hasLabels; + if (UNEXPECTED(!containsLabels(stmts, hasLabels))) return zv::Val(); + zend_long count = 0; + zv::Val constraints; + for (;;) { + zv::Val siteTypes = staticSiteTypes(staticSites, types.raw()); + if (UNEXPECTED(siteTypes.isUndef())) return zv::Val(); + zv::Val observingFrame = pt_template_argument_frame_new(parentFrame, NULL, NULL, parentNode, stmts); + if (UNEXPECTED(observingFrame.isUndef())) return zv::Val(); + zv::Val frame = pt_template_argument_frame_with_static_variable_types(observingFrame.raw(), siteTypes.raw(), staticStatementIndexes); + if (UNEXPECTED(frame.isUndef())) return zv::Val(); + + zval *startEntry = entryAt(entries, start); + if (UNEXPECTED(startEntry == NULL)) return zv::Val(); + zv::Val state = cloneObject(entryState(startEntry)); + if (UNEXPECTED(state.isUndef())) return zv::Val(); + { + zval *clonedScope = OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope); + zv::Val reframed = pt_mutating_scope_with_template_argument_frame(Z_OBJ_P(clonedScope), frame.raw()); + if (UNEXPECTED(reframed.isUndef())) return zv::Val(); + zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope)).assign(std::move(reframed)); + } + zv::Val walkStorage = pt_expression_result_storage_duplicate(storageArg); + if (UNEXPECTED(walkStorage.isUndef())) return zv::Val(); + zv::Val walkRecording = newRecordingNodeCallback(); + if (UNEXPECTED(walkRecording.isUndef())) return zv::Val(); + zv::Arr replayedScopes = zv::Arr::empty(); + zv::Val suspendedGatherers = pt_node_scope_resolver_suspend_node_gatherers(nodeScopeResolver); + if (UNEXPECTED(suspendedGatherers.isUndef())) return zv::Val(); + zv::Val pushedScope = zv::Val::copyOf(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope))); + if (UNEXPECTED(!pt_mutating_scope_push_expression_result_storage(Z_OBJ_P(pushedScope.raw()), walkStorage.raw()))) { + (void) pt_node_scope_resolver_restore_node_gatherers(nodeScopeResolver, suspendedGatherers.raw()); + return zv::Val(); + } + (void) observeStaticVariablesFrom(nodeScopeResolver, parentNode, stmts, stmtsTable, frame.raw(), entries, state.raw(), walkStorage.raw(), walkRecording.raw(), context, start, stmtCount, hasLabels, replayedScopes); + pt_finally([&]() { + (void) pt_mutating_scope_pop_expression_result_storage(Z_OBJ_P(pushedScope.raw())); + (void) pt_node_scope_resolver_restore_node_gatherers(nodeScopeResolver, suspendedGatherers.raw()); + }); + if (UNEXPECTED(EG(exception))) return zv::Val(); + + zval *stateScope = OBJ_PROP_NUM(Z_OBJ_P(state.raw()), stateSlots::scope); + constraints = pt_mutating_scope_get_template_argument_constraints(Z_OBJ_P(stateScope)); + if (UNEXPECTED(constraints.isUndef())) return zv::Val(); + if (constraints.isNull()) { + constraints = templateArgumentConstraintsCreateEmpty(); + if (UNEXPECTED(constraints.isUndef())) return zv::Val(); + } + + zv::Val walkTypes = collectStaticVariableTypes(bodyScope.raw(), names.raw(), walkRecording.raw(), state.raw(), replayedScopes.raw()); + if (UNEXPECTED(walkTypes.isUndef())) return zv::Val(); + bool converged = true; + for (auto entry : zv::ArrRef(names.raw())) { + zend_string *name = Z_STR_P(entry.value().raw()); + HashTable *known = Z_ARRVAL_P(zend_hash_find(Z_ARRVAL_P(types.raw()), name)); + HashTable *walked = Z_ARRVAL_P(zend_hash_find(Z_ARRVAL_P(walkTypes.raw()), name)); + bool holds = true; + for (zend_ulong k = 0; k < 2 && holds; k++) { + zval *knownType = zend_hash_index_find(known, k); + zv::Val isSuperType = pt_type_op(Z_OBJ_P(knownType), PT_OP_IS_SUPER_TYPE_OF, 1, zend_hash_index_find(walked, k)); + if (UNEXPECTED(isSuperType.isUndef())) return zv::Val(); + holds = pt_type_result_trinary(isSuperType.raw()) == PT_TRI_YES; + } + if (!holds) { + converged = false; + break; + } + } + count++; + if (converged || count >= PT_SH_LOOP_SCOPE_ITERATIONS_LIMIT) break; + + types = joinStaticVariableTypes(bodyScope.raw(), names.raw(), types.raw(), walkTypes.raw(), count > PT_SH_GENERALIZE_AFTER_ITERATION_LIMIT); + if (UNEXPECTED(types.isUndef())) return zv::Val(); + } + + zv::Val siteTypes = staticSiteTypes(staticSites, types.raw()); + if (UNEXPECTED(siteTypes.isUndef())) return zv::Val(); + zv::Arr result = zv::Arr::create(2); + result.push(std::move(siteTypes)); + result.push(std::move(constraints)); + return zv::Val(std::move(result)); + } + + /* inferStaticVariableTypes()'s try block: the statements from $start on, + * walked or carried over, each carried-over entry scope into + * $replayedScopes; false = pending exception */ + bool observeStaticVariablesFrom(zval *nodeScopeResolver, zval *parentNode, zval *stmts, HashTable *stmtsTable, zval *frame, zval *entries, zval *state, zval *storage, zval *nodeCallback, zval *context, zend_long start, zend_long stmtCount, bool hasLabels, zv::Arr &replayedScopes) + { + for (zend_long i = start; i < stmtCount; i++) { + zval *recordedEntryPair = entryAt(entries, i); + if (UNEXPECTED(recordedEntryPair == NULL)) return false; + zval *recordedEntry = entryState(recordedEntryPair); + zend_object *stateObject = Z_OBJ_P(state); + 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 false; + } + if (differingRoots.ref().isArray() && zend_hash_num_elements(Z_ARRVAL_P(differingRoots.raw())) == 0) { + bool hasSite; + if (UNEXPECTED(!frameHasSiteAtOrAfter(frame, i, hasSite))) return false; + if (!hasSite) { + // converged with the observation pass: the rest of it stands + zval *finalEntryPair = entryAt(entries, stmtCount); + if (UNEXPECTED(finalEntryPair == NULL)) return false; + zval *finalState = entryState(finalEntryPair); + zv::Val carried = withRecordedConstraints(OBJ_PROP_NUM(stateObject, stateSlots::scope), OBJ_PROP_NUM(recordedEntryObject, stateSlots::scope), OBJ_PROP_NUM(Z_OBJ_P(finalState), stateSlots::scope)); + if (UNEXPECTED(carried.isUndef())) return false; + zv::Ref(OBJ_PROP_NUM(stateObject, stateSlots::scope)).assign(std::move(carried)); + return appendRecordedStatementResults(state, recordedEntry, finalState); + } + } + + zval *stmt = zend_hash_index_find(stmtsTable, (zend_ulong) i); + if (UNEXPECTED(stmt == NULL)) { + zend_throw_error(NULL, "phpstan_turbo: the function-like body's statements are not a list"); + return false; + } + ZVAL_DEREF(stmt); + bool reWalk = differingRoots.isNull() || hasLabels; + if (!reWalk && UNEXPECTED(!frameOwnsSiteInStatement(frame, i, reWalk))) return false; + if (!reWalk && UNEXPECTED(!statementMentionsAnyVariable(stmt, differingRoots.raw(), reWalk))) return false; + if (reWalk) { + if (UNEXPECTED(!processStatementStep(nodeScopeResolver, parentNode, stmts, i, stmt, state, storage, nodeCallback, context, true))) return false; + replayedScopes.push(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope))); + continue; + } + + // the statement does not read what changed: the variables keep + // their types through it + replayedScopes.push(zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope))); + zval *recordedExitPair = entryAt(entries, i + 1); + if (UNEXPECTED(recordedExitPair == NULL)) return false; + zval *recordedExit = entryState(recordedExitPair); + if (UNEXPECTED(!appendRecordedStatementResults(state, recordedEntry, recordedExit))) return false; + zval *currentScope = OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope); + zval *recordedEntryScope = OBJ_PROP_NUM(recordedEntryObject, stateSlots::scope); + zval *recordedExitScope = OBJ_PROP_NUM(Z_OBJ_P(recordedExit), stateSlots::scope); + zv::Val delta = pt_mutating_scope_with_recorded_statement_delta(Z_OBJ_P(currentScope), Z_OBJ_P(recordedEntryScope), Z_OBJ_P(recordedExitScope)); + if (UNEXPECTED(delta.isUndef())) return false; + zv::Val carried = withRecordedConstraints(delta.raw(), recordedEntryScope, recordedExitScope); + if (UNEXPECTED(carried.isUndef())) return false; + zv::Ref(OBJ_PROP_NUM(Z_OBJ_P(state), stateSlots::scope)).assign(std::move(carried)); + } + return true; + } + + /* spl_object_id($var) => [$var, $type, $nativeType] of every site; + * UNDEF = pending exception */ + static zv::Val staticSiteTypes(zval *staticSites, zval *types) + { + zv::Arr siteTypes = zv::Arr::empty(); + for (auto entry : zv::ArrRef(staticSites)) { + HashTable *site = Z_ARRVAL_P(entry.value().deref().raw()); + zval *var = zend_hash_index_find(site, 0); + zval *name = zend_hash_index_find(site, 2); + if (UNEXPECTED(var == NULL || name == NULL || Z_TYPE_P(name) != IS_STRING)) continue; + zval *pair = zend_hash_find(Z_ARRVAL_P(types), Z_STR_P(name)); + if (UNEXPECTED(pair == NULL)) { + zend_error(E_WARNING, "Undefined array key \"%s\"", Z_STRVAL_P(name)); + if (UNEXPECTED(EG(exception))) return zv::Val(); + continue; + } + zv::Arr siteType = zv::Arr::create(3); + siteType.push(zv::Ref(var)); + siteType.push(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(pair), 0))); + siteType.push(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(pair), 1))); + siteTypes.separate(); + zval value; + ZVAL_COPY_VALUE(&value, siteType.raw()); + ZVAL_UNDEF(siteType.raw()); + zend_hash_index_update(siteTypes.table(), Z_OBJ_HANDLE_P(var), &value); + } + return zv::Val(std::move(siteTypes)); + } + + /* one scope's contribution to collectStaticVariableTypes(); false = + * pending exception */ + [[nodiscard]] static bool collectStaticVariableScope(zval *scope, zval *bodyFunction, zval *bodyReflection, zval *names, zv::ScratchTable &seen, zv::Arr &phpDocTypes, zv::Arr &nativeTypes) + { + if (Z_TYPE_P(scope) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(scope), pt_ce_mutating_scope)) return true; + zend_ulong id = Z_OBJ_HANDLE_P(scope); + if (zend_hash_index_exists(seen.table(), id)) return true; + zval marked; + ZVAL_TRUE(&marked); + zend_hash_index_add(seen.table(), id, &marked); + zv::Val reflection = pt_mutating_scope_get_anonymous_function_reflection(Z_OBJ_P(scope)); + if (UNEXPECTED(reflection.isUndef())) return false; + if (!zend_is_identical(reflection.raw(), bodyReflection)) return true; + zv::Val function = pt_mutating_scope_get_function(Z_OBJ_P(scope)); + if (UNEXPECTED(function.isUndef())) return false; + if (!zend_is_identical(function.raw(), bodyFunction)) return true; + for (auto entry : zv::ArrRef(names)) { + zend_string *name = Z_STR_P(entry.value().raw()); + zv::Val has = pt_mutating_scope_has_variable_type(Z_OBJ_P(scope), name); + if (UNEXPECTED(has.isUndef())) return false; + if (pt_type_trinary_value(has.raw()) == PT_TRI_NO) continue; + zv::Val type = pt_mutating_scope_get_variable_type(Z_OBJ_P(scope), name); + if (UNEXPECTED(type.isUndef())) return false; + zv::Val nativeScope = pt_mutating_scope_do_not_treat_phpdoc_types_as_certain(Z_OBJ_P(scope)); + if (UNEXPECTED(nativeScope.isUndef())) return false; + zv::Val nativeType = pt_mutating_scope_get_variable_type(Z_OBJ_P(nativeScope.raw()), name); + if (UNEXPECTED(nativeType.isUndef())) return false; + zval *phpDocList = zend_hash_find(phpDocTypes.table(), name); + zval *nativeList = zend_hash_find(nativeTypes.table(), name); + SEPARATE_ARRAY(phpDocList); + SEPARATE_ARRAY(nativeList); + zval typeZv = type.take(); + zend_hash_next_index_insert(Z_ARRVAL_P(phpDocList), &typeZv); + zval nativeTypeZv = nativeType.take(); + zend_hash_next_index_insert(Z_ARRVAL_P(nativeList), &nativeTypeZv); + } + return true; + } + + /* the private collectStaticVariableTypes(): name => [phpdoc, native]; + * UNDEF = pending exception */ + static zv::Val collectStaticVariableTypes(zval *bodyScope, zval *names, zval *recording, zval *endState, zval *moreScopes) + { + zv::Arr scopes = zv::Arr::empty(); + for (auto entry : zv::ArrRef(moreScopes)) { + scopes.push(zv::Ref(entry.value().deref().raw())); + } + zend_object *endStateObject = Z_OBJ_P(endState); + scopes.push(zv::Ref(OBJ_PROP_NUM(endStateObject, stateSlots::scope))); + zval *exitPoints = OBJ_PROP_NUM(endStateObject, stateSlots::exitPoints); + if (Z_TYPE_P(exitPoints) == IS_ARRAY) { + for (auto entry : zv::ArrRef(exitPoints)) { + zv::Val hold; + zval *scope = pt_internal_statement_exit_point_scope(entry.value().deref().raw(), hold); + if (UNEXPECTED(scope == NULL)) return zv::Val(); + scopes.push(zv::Ref(scope)); + } + } + zval *throwPoints = OBJ_PROP_NUM(endStateObject, stateSlots::throwPoints); + if (Z_TYPE_P(throwPoints) == IS_ARRAY) { + for (auto entry : zv::ArrRef(throwPoints)) { + zv::Val hold; + zval *scope = pt_internal_throw_point_scope(entry.value().deref().raw(), hold); + if (UNEXPECTED(scope == NULL)) return zv::Val(); + scopes.push(zv::Ref(scope)); + } + } + zval *pairs = OBJ_PROP_NUM(Z_OBJ_P(recording), ptdecl::RecordingNodeCallback::slot::pairs); + if (Z_TYPE_P(pairs) == IS_ARRAY) { + for (auto entry : zv::ArrRef(pairs)) { + zval *scope = zend_hash_index_find(Z_ARRVAL_P(entry.value().deref().raw()), 1); + if (scope != NULL) scopes.push(zv::Ref(scope)); + } + } + + zv::Val bodyFunction = pt_mutating_scope_get_function(Z_OBJ_P(bodyScope)); + if (UNEXPECTED(bodyFunction.isUndef())) return zv::Val(); + zv::Val bodyReflection = pt_mutating_scope_get_anonymous_function_reflection(Z_OBJ_P(bodyScope)); + if (UNEXPECTED(bodyReflection.isUndef())) return zv::Val(); + zv::Arr phpDocTypes = zv::Arr::create(0); + zv::Arr nativeTypes = zv::Arr::create(0); + for (auto entry : zv::ArrRef(names)) { + phpDocTypes.set(Z_STR_P(entry.value().raw()), zv::Val(zv::Arr::empty())); + nativeTypes.set(Z_STR_P(entry.value().raw()), zv::Val(zv::Arr::empty())); + } + zv::ScratchTable seen(64); + for (auto entry : zv::ArrRef(scopes.raw())) { + if (UNEXPECTED(!collectStaticVariableScope(entry.value().raw(), bodyFunction.raw(), bodyReflection.raw(), names, seen, phpDocTypes, nativeTypes))) return zv::Val(); + } + + zv::Arr types = zv::Arr::create(0); + for (auto entry : zv::ArrRef(names)) { + zend_string *name = Z_STR_P(entry.value().raw()); + HashTable *phpDocList = Z_ARRVAL_P(zend_hash_find(phpDocTypes.table(), name)); + HashTable *nativeList = Z_ARRVAL_P(zend_hash_find(nativeTypes.table(), name)); + zv::Arr pair = zv::Arr::create(2); + if (zend_hash_num_elements(phpDocList) == 0) { + zv::Val never = pt_type_new_never_type(); + if (UNEXPECTED(never.isUndef())) return zv::Val(); + zv::Val nativeNever = pt_type_new_never_type(); + if (UNEXPECTED(nativeNever.isUndef())) return zv::Val(); + pair.push(std::move(never)); + pair.push(std::move(nativeNever)); + } else { + zv::Val phpDocUnion = unionOfList(phpDocList); + if (UNEXPECTED(phpDocUnion.isUndef())) return zv::Val(); + zv::Val nativeUnion = unionOfList(nativeList); + if (UNEXPECTED(nativeUnion.isUndef())) return zv::Val(); + pair.push(std::move(phpDocUnion)); + pair.push(std::move(nativeUnion)); + } + types.set(name, zv::Val(std::move(pair))); + } + return zv::Val(std::move(types)); + } + + /* TypeCombinator::union(...$list); UNDEF = pending exception */ + static zv::Val unionOfList(HashTable *list) + { + uint32_t n = zend_hash_num_elements(list); + zval *argv = (zval *) safe_emalloc(n, sizeof(zval), 0); + uint32_t k = 0; + for (auto entry : zv::TableRef(list)) { + ZVAL_COPY_VALUE(&argv[k++], entry.value().raw()); + } + zv::Val result = pt_type_combinator_union(n, argv); + efree(argv); + return result; + } + + /* the private joinStaticVariableTypes(); UNDEF = pending exception */ + static zv::Val joinStaticVariableTypes(zval *bodyScope, zval *names, zval *types, zval *walkTypes, bool generalize) + { + zv::Arr joined = zv::Arr::create(0); + for (auto entry : zv::ArrRef(names)) { + zend_string *name = Z_STR_P(entry.value().raw()); + HashTable *known = Z_ARRVAL_P(zend_hash_find(Z_ARRVAL_P(types), name)); + HashTable *walked = Z_ARRVAL_P(zend_hash_find(Z_ARRVAL_P(walkTypes), name)); + zv::Arr pair = zv::Arr::create(2); + for (zend_ulong k = 0; k < 2; k++) { + zval argv[2]; + ZVAL_COPY_VALUE(&argv[0], zend_hash_index_find(known, k)); + ZVAL_COPY_VALUE(&argv[1], zend_hash_index_find(walked, k)); + zv::Val unioned = pt_type_combinator_union(2, argv); + if (UNEXPECTED(unioned.isUndef())) return zv::Val(); + pair.push(std::move(unioned)); + } + joined.set(name, zv::Val(std::move(pair))); + } + if (!generalize) return zv::Val(std::move(joined)); + + zv::Val previousScope = zv::Val::copyOf(zv::Ref(bodyScope)); + zv::Val joinedScope = zv::Val::copyOf(zv::Ref(bodyScope)); + zv::Arr writableNames = zv::Arr::create(0); + for (auto entry : zv::ArrRef(names)) { + zend_string *name = Z_STR_P(entry.value().raw()); + HashTable *known = Z_ARRVAL_P(zend_hash_find(Z_ARRVAL_P(types), name)); + HashTable *joinedPair = Z_ARRVAL_P(zend_hash_find(joined.table(), name)); + zv::Val assignedPrevious = pt_mutating_scope_assign_variable(Z_OBJ_P(previousScope.raw()), name, zend_hash_index_find(known, 0), zend_hash_index_find(known, 1), pt_trinary_singleton(PT_TRI_YES)); + if (UNEXPECTED(assignedPrevious.isUndef())) return zv::Val(); + previousScope = std::move(assignedPrevious); + zv::Val assignedJoined = pt_mutating_scope_assign_variable(Z_OBJ_P(joinedScope.raw()), name, zend_hash_index_find(joinedPair, 0), zend_hash_index_find(joinedPair, 1), pt_trinary_singleton(PT_TRI_YES)); + if (UNEXPECTED(assignedJoined.isUndef())) return zv::Val(); + joinedScope = std::move(assignedJoined); + writableNames.set(name, zv::Val::boolean(true)); + } + zv::Val generalizedScope = pt_mutating_scope_generalize_with_names(Z_OBJ_P(previousScope.raw()), Z_OBJ_P(joinedScope.raw()), writableNames.raw()); + if (UNEXPECTED(generalizedScope.isUndef())) return zv::Val(); + zv::Val nativeScope = pt_mutating_scope_do_not_treat_phpdoc_types_as_certain(Z_OBJ_P(generalizedScope.raw())); + if (UNEXPECTED(nativeScope.isUndef())) return zv::Val(); + zv::Arr generalized = zv::Arr::create(0); + for (auto entry : zv::ArrRef(names)) { + zend_string *name = Z_STR_P(entry.value().raw()); + zv::Val type = pt_mutating_scope_get_variable_type(Z_OBJ_P(generalizedScope.raw()), name); + if (UNEXPECTED(type.isUndef())) return zv::Val(); + zv::Val nativeType = pt_mutating_scope_get_variable_type(Z_OBJ_P(nativeScope.raw()), name); + if (UNEXPECTED(nativeType.isUndef())) return zv::Val(); + zv::Arr pair = zv::Arr::create(2); + pair.push(std::move(type)); + pair.push(std::move(nativeType)); + generalized.set(name, zv::Val(std::move(pair))); + } + return zv::Val(std::move(generalized)); + } + /* the private observeClosureSignatures() */ zv::Val observeClosureSignatures(zval *nodeScopeResolver, zval *parentNode, zval *stmts, zval *frame, zval *entries, zval *storageArg, zval *context, zval *statementStartTokenPositions) { @@ -2177,16 +2593,17 @@ PT_MINIT_REGISTRATION(pt_register_statements_handler) /* the real parameter class names: the DI container autowires the * service by reflecting the constructor */ cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *fileTypeMapper, *templateArgumentObserver, *templateArgumentResolver, *container; + zval *fileTypeMapper, *templateArgumentObserver, *templateArgumentResolver, *container, *staticVariableInference; bool unresolvedTemplateArguments; - ZEND_PARSE_PARAMETERS_START(5, 5) + ZEND_PARSE_PARAMETERS_START(6, 6) Z_PARAM_OBJECT(fileTypeMapper) Z_PARAM_OBJECT(templateArgumentObserver) Z_PARAM_OBJECT(templateArgumentResolver) Z_PARAM_OBJECT(container) + Z_PARAM_OBJECT(staticVariableInference) Z_PARAM_BOOL(unresolvedTemplateArguments) ZEND_PARSE_PARAMETERS_END(); - StatementsHandler(Z_OBJ_P(ZEND_THIS)).construct(fileTypeMapper, templateArgumentObserver, templateArgumentResolver, container, unresolvedTemplateArguments); + StatementsHandler(Z_OBJ_P(ZEND_THIS)).construct(fileTypeMapper, templateArgumentObserver, templateArgumentResolver, container, staticVariableInference, unresolvedTemplateArguments); }); cls.method(sigs::processNodesWithStorage, [](INTERNAL_FUNCTION_PARAMETERS) { diff --git a/turbo-ext/src/StaticVariableHandler.cpp b/turbo-ext/src/StaticVariableHandler.cpp index 1ed5a66800d..ea175fda2a5 100644 --- a/turbo-ext/src/StaticVariableHandler.cpp +++ b/turbo-ext/src/StaticVariableHandler.cpp @@ -54,10 +54,11 @@ class StaticVariableHandler public: explicit StaticVariableHandler(zend_object *self) : self(self) {} - /* the constructor body: the promoted property */ - void construct(zval *varAnnotationProcessor) + /* the constructor body: the promoted properties */ + void construct(zval *varAnnotationProcessor, zval *staticVariableInference) { zv::ObjRef(self).propAtWrite(slots::varAnnotationProcessor, zv::Val::copyOf(zv::Ref(varAnnotationProcessor))); + zv::ObjRef(self).propAtWrite(slots::staticVariableInference, zv::Val::copyOf(zv::Ref(staticVariableInference))); } /* Mirrors supports(); false = pending exception */ @@ -122,7 +123,7 @@ class StaticVariableHandler } /* the loop body over one static variable; false = pending exception */ - [[nodiscard]] static bool processVar(zval *nodeScopeResolver, zval *stmt, zval *staticVar, zv::Val &scope, zval *storage, zval *nodeCallback, zval *context, zv::Arr &impurePoints, zv::Arr &vars, zv::Arr &variableFlows) + [[nodiscard]] bool processVar(zval *nodeScopeResolver, zval *stmt, zval *staticVar, zv::Val &scope, zval *storage, zval *nodeCallback, zval *context, zv::Arr &impurePoints, zv::Arr &vars, zv::Arr &variableFlows) const { if (UNEXPECTED(Z_TYPE_P(staticVar) != IS_OBJECT)) { zend_error(E_WARNING, "Attempt to read property \"var\" on %s", zend_zval_value_name(staticVar)); @@ -150,11 +151,12 @@ class StaticVariableHandler zval *defaultValue = ptsh::readNodeProperty(pt_svh_static_var_default_site, staticVar, PT_LC("default")); if (UNEXPECTED(defaultValue == NULL)) return false; + zv::Val defaultExprResult = zv::Val::null(); if (Z_TYPE_P(defaultValue) != IS_NULL) { zv::Val defaultHold = zv::Val::copyOf(zv::Ref(defaultValue)); zv::Val expressionContext = deepContext(context); if (UNEXPECTED(expressionContext.isUndef())) return false; - zv::Val defaultExprResult = pt_node_scope_resolver_process_expr_node(nodeScopeResolver, stmt, defaultHold.raw(), scope.raw(), storage, nodeCallback, expressionContext.raw()); + defaultExprResult = pt_node_scope_resolver_process_expr_node(nodeScopeResolver, stmt, defaultHold.raw(), scope.raw(), storage, nodeCallback, expressionContext.raw()); if (UNEXPECTED(defaultExprResult.isUndef())) return false; zv::Val variableFlow = pt_expression_result_variable_flow(defaultExprResult.raw()); if (UNEXPECTED(variableFlow.isUndef())) return false; @@ -201,12 +203,36 @@ class StaticVariableHandler return false; } zv::Val nameHold = zv::Val::copyOf(zv::Ref(varName)); - zval mixed; - if (UNEXPECTED(!pt_mixed_type_new(&mixed))) return false; - zv::Val type = zv::Val::adopt(mixed); - zval nativeMixed; - if (UNEXPECTED(!pt_mixed_type_new(&nativeMixed))) return false; - zv::Val nativeType = zv::Val::adopt(nativeMixed); + // the type the previous calls may have left - see StaticVariableInference + zval *inference = OBJ_PROP_NUM(self, slots::staticVariableInference); + zv::Val type, nativeType; + zv::Val types = pt_static_variable_inference_get_resolved_types(inference, scope.raw(), varHold.raw()); + if (UNEXPECTED(types.isUndef())) return false; + if (types.ref().isArray()) { + type = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(types.raw()), 0))); + nativeType = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(types.raw()), 1))); + } else { + bool inferred; + if (UNEXPECTED(!pt_static_variable_inference_is_inferred(inference, scope.raw(), varHold.raw(), inferred))) return false; + if (inferred && !defaultExprResult.isNull()) { + type = pt_expression_result_get_type(defaultExprResult.raw()); + if (UNEXPECTED(type.isUndef())) return false; + nativeType = pt_expression_result_get_native_type(defaultExprResult.raw()); + if (UNEXPECTED(nativeType.isUndef())) return false; + } else if (inferred) { + zval nullType, nativeNullType; + if (UNEXPECTED(!pt_null_type_new(&nullType))) return false; + type = zv::Val::adopt(nullType); + if (UNEXPECTED(!pt_null_type_new(&nativeNullType))) return false; + nativeType = zv::Val::adopt(nativeNullType); + } else { + zval mixed, nativeMixed; + if (UNEXPECTED(!pt_mixed_type_new(&mixed))) return false; + type = zv::Val::adopt(mixed); + if (UNEXPECTED(!pt_mixed_type_new(&nativeMixed))) return false; + nativeType = zv::Val::adopt(nativeMixed); + } + } zv::Val assigned = pt_mutating_scope_assign_variable(Z_OBJ_P(scope.raw()), Z_STR_P(nameHold.raw()), type.raw(), nativeType.raw(), pt_trinary_singleton(PT_TRI_YES)); if (UNEXPECTED(assigned.isUndef())) return false; scope = std::move(assigned); @@ -238,9 +264,9 @@ PT_MINIT_REGISTRATION(pt_register_static_variable_handler) /* the real parameter class names: the DI container autowires the * service by reflecting the constructor */ cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *varAnnotationProcessor; - if (!zp::parse(execute_data, varAnnotationProcessor)) RETURN_THROWS(); - StaticVariableHandler(Z_OBJ_P(ZEND_THIS)).construct(varAnnotationProcessor); + zval *varAnnotationProcessor, *staticVariableInference; + if (!zp::parse(execute_data, varAnnotationProcessor, staticVariableInference)) RETURN_THROWS(); + StaticVariableHandler(Z_OBJ_P(ZEND_THIS)).construct(varAnnotationProcessor, staticVariableInference); }); cls.method<&StaticVariableHandler::supports, zp::Obj>(sigs::supports); diff --git a/turbo-ext/src/StaticVariableInference.cpp b/turbo-ext/src/StaticVariableInference.cpp new file mode 100644 index 00000000000..3107efebf6f --- /dev/null +++ b/turbo-ext/src/StaticVariableInference.cpp @@ -0,0 +1,486 @@ +/* + * PHPStanTurbo\StaticVariableInference — native implementation of + * PHPStan\Analyser\Generics\StaticVariableInference. + * + * A DI service (#[AutowiredService]): the constructor keeps the twin's + * arginfo so Nette autowires it. getSites() scans a function-like body once + * (cached in a node attribute) for the `static` variables whose type is + * inferred; isInferred() / getResolvedTypes() answer StaticVariableHandler + * from the current template argument frame. The direct entries + * pt_static_variable_inference_*() serve the native StaticVariableHandler and + * StatementsHandler. + */ + +#include "support.h" +#include "generated/StaticVariableInference.h" + +namespace slots = ptdecl::StaticVariableInference::slot; +namespace sigs = ptdecl::StaticVariableInference::sig; +#include "zv.h" +#include "TypeTraits.h" +#include "Engine.h" +#include "ClosureSupport.h" + +zend_class_entry *pt_ce_static_variable_inference = nullptr; + +namespace { + +/* the twin's SITES_ATTRIBUTE, a permanent interned string (module startup) */ +zend_string *pt_svi_sites_attribute = nullptr; + +pt_method_site pt_svi_get_sub_node_names_site; +pt_method_site pt_svi_to_lower_string_site; +pt_property_site pt_svi_variable_name_site; +pt_property_site pt_svi_func_call_name_site; +pt_property_site pt_svi_assign_var_site; +pt_property_site pt_svi_assign_expr_site; +pt_property_site pt_svi_global_vars_site; +pt_property_site pt_svi_static_vars_site; +pt_property_site pt_svi_static_var_var_site; +pt_property_site pt_svi_fetch_var_site; +pt_property_site pt_svi_items_site; +pt_property_site pt_svi_item_value_site; + +/* $value instanceof ; false = pending exception */ +[[nodiscard]] bool isA(zval *value, int classIdx, bool &out) +{ + int is = ptclosure::instanceOf(value, classIdx); + if (UNEXPECTED(is < 0)) return false; + out = is == 1; + return true; +} + +inline zval *read(pt_property_site &site, zval *node, const char *name, size_t len) +{ + return ptclosure::prop(site, node, name, len); +} + +/* the twin's `foreach ($node->getSubNodeNames() as $subNodeName) { ... }` + * pushing every Node found (directly or in an array) onto the stack; false = + * pending exception */ +[[nodiscard]] bool pushSubNodes(zval *node, zv::Arr &stack) +{ + zv::Val names = pt_call_method_cached(pt_svi_get_sub_node_names_site, Z_OBJ_P(node), PT_LC("getsubnodenames"), 0, NULL); + if (UNEXPECTED(names.isUndef())) return false; + if (UNEXPECTED(!names.ref().isArray())) { + zend_error(E_WARNING, "foreach() argument must be of type array|object, %s given", zend_zval_value_name(names.raw())); + return EG(exception) == NULL; + } + zend_class_entry *nodeCe = pt_class(PT_CLASS_NODE); + if (UNEXPECTED(nodeCe == NULL)) return false; + zend_object *nodeObject = Z_OBJ_P(node); + for (auto entry : zv::ArrRef(names.raw())) { + zend_string *subNodeName = zval_get_string(entry.value().deref().raw()); + zval rv; + ZVAL_UNDEF(&rv); + zval *subNode = zend_read_property_ex(nodeObject->ce, nodeObject, subNodeName, 0, &rv); + zend_string_release(subNodeName); + if (UNEXPECTED(EG(exception))) { + zval_ptr_dtor(&rv); + return false; + } + zv::Val held = zv::Val::copyOf(zv::Ref(subNode).deref()); + zval_ptr_dtor(&rv); + if (held.ref().isObject() && instanceof_function(Z_OBJCE_P(held.raw()), nodeCe)) { + stack.push(std::move(held)); + } else if (held.ref().isArray()) { + for (auto item : zv::ArrRef(held.raw())) { + zval *value = item.value().deref().raw(); + if (Z_TYPE_P(value) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(value), nodeCe)) continue; + stack.push(zv::Val::copyOf(zv::Ref(value))); + } + } + } + return true; +} + +/* array_pop($stack), or UNDEF for an empty stack */ +zv::Val popNode(zv::Arr &stack) +{ + HashTable *table = stack.table(); + uint32_t count = zend_hash_num_elements(table); + if (count == 0) return zv::Val(); + stack.separate(); + table = stack.table(); + zval *last = zend_hash_index_find(table, count - 1); + zv::Val value = zv::Val::copyOf(zv::Ref(last)); + zend_hash_index_del(table, count - 1); + table->nNextFreeElement = count - 1; + return value; +} + +/* preg_match('~@(?:phpstan-|psalm-)?var\s~', $text) === 1 */ +bool declaresVar(zend_string *text) +{ + const char *s = ZSTR_VAL(text); + size_t n = ZSTR_LEN(text); + auto startsWith = [&](size_t at, const char *prefix, size_t len) { + return at + len <= n && memcmp(s + at, prefix, len) == 0; + }; + for (size_t i = 0; i < n; i++) { + if (s[i] != '@') continue; + // the regex backtracks from a prefix to the bare tag + size_t candidates[] = {i + 1, i + 9, i + 7}; + bool variants[] = {true, startsWith(i + 1, "phpstan-", 8), startsWith(i + 1, "psalm-", 6)}; + for (int k = 0; k < 3; k++) { + size_t at = candidates[k]; + if (!variants[k] || !startsWith(at, "var", 3) || at + 3 >= n) continue; + char c = s[at + 3]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f') return true; + } + } + return false; +} + +/* Mirrors the private hasVarTag(); false = pending exception */ +[[nodiscard]] bool hasVarTag(zval *stmt, bool &out) +{ + zv::Val text = pt_node_doc_comment_text(stmt); + if (UNEXPECTED(text.isUndef())) return false; + out = text.ref().isString() && declaresVar(Z_STR_P(text.raw())); + return true; +} + +/* Mirrors the private collectRootNames(); false = pending exception */ +[[nodiscard]] bool collectRootNames(zval *exprArg, zv::Arr &names) +{ + zv::Val expr = zv::Val::copyOf(zv::Ref(exprArg)); + for (;;) { + bool fetch; + if (UNEXPECTED(!isA(expr.raw(), PT_CLASS_ARRAY_DIM_FETCH, fetch))) return false; + if (!fetch && UNEXPECTED(!isA(expr.raw(), PT_CLASS_PROPERTY_FETCH, fetch))) return false; + if (!fetch && UNEXPECTED(!isA(expr.raw(), PT_CLASS_NULLSAFE_PROPERTY_FETCH, fetch))) return false; + bool staticFetch = false; + if (!fetch && UNEXPECTED(!isA(expr.raw(), PT_CLASS_STATIC_PROPERTY_FETCH, staticFetch))) return false; + if (staticFetch) return true; + if (!fetch) break; + zval *var = read(pt_svi_fetch_var_site, expr.raw(), PT_LC("var")); + if (UNEXPECTED(var == NULL)) return false; + expr = zv::Val::copyOf(zv::Ref(var)); + } + bool isVariable; + if (UNEXPECTED(!isA(expr.raw(), PT_CLASS_VARIABLE, isVariable))) return false; + if (isVariable) { + zval *name = read(pt_svi_variable_name_site, expr.raw(), PT_LC("name")); + if (UNEXPECTED(name == NULL)) return false; + if (Z_TYPE_P(name) == IS_STRING) { + names.set(Z_STR_P(name), zv::Val::boolean(true)); + } + return true; + } + bool isList, isArray = false; + if (UNEXPECTED(!isA(expr.raw(), PT_CLASS_LIST_EXPR, isList))) return false; + if (!isList && UNEXPECTED(!isA(expr.raw(), PT_CLASS_ARRAY_EXPR, isArray))) return false; + if (!isList && !isArray) return true; + + zval *items = read(pt_svi_items_site, expr.raw(), PT_LC("items")); + if (UNEXPECTED(items == NULL)) return false; + if (UNEXPECTED(Z_TYPE_P(items) != IS_ARRAY)) return true; + zv::Val itemsHold = zv::Val::copyOf(zv::Ref(items)); + for (auto entry : zv::ArrRef(itemsHold.raw())) { + zval *item = entry.value().deref().raw(); + if (Z_TYPE_P(item) == IS_NULL) continue; + zval *value = read(pt_svi_item_value_site, item, PT_LC("value")); + if (UNEXPECTED(value == NULL)) return false; + zv::Val valueHold = zv::Val::copyOf(zv::Ref(value)); + bool ok = true; + pt_engine_with_stack([&]() { ok = collectRootNames(valueHold.raw(), names); }); + if (UNEXPECTED(!ok)) return false; + } + return true; +} + +/* one node of scanSites()'s walk: false = pending exception; $opaque set + * when the body's variables can change behind the analysis' back */ +[[nodiscard]] bool scanNode(zval *n, zend_long index, zv::Arr &candidates, zv::Arr &excludedNames, bool &skip, bool &opaque) +{ + skip = false; + opaque = false; + bool is; + if (UNEXPECTED(!isA(n, PT_CLASS_FUNCTION_LIKE, is))) return false; + if (!is && UNEXPECTED(!isA(n, PT_CLASS_CLASS_LIKE_STMT, is))) return false; + if (is) { + skip = true; + return true; + } + if (UNEXPECTED(!isA(n, PT_CLASS_YIELD, is))) return false; + if (!is && UNEXPECTED(!isA(n, PT_CLASS_YIELD_FROM, is))) return false; + if (!is && UNEXPECTED(!isA(n, PT_CLASS_INCLUDE_EXPR, is))) return false; + if (!is && UNEXPECTED(!isA(n, PT_CLASS_EVAL_EXPR, is))) return false; + if (is) { + opaque = true; + return true; + } + if (UNEXPECTED(!isA(n, PT_CLASS_VARIABLE, is))) return false; + if (is) { + zval *name = read(pt_svi_variable_name_site, n, PT_LC("name")); + if (UNEXPECTED(name == NULL)) return false; + if (Z_TYPE_P(name) != IS_STRING) { + opaque = true; + return true; + } + } + if (UNEXPECTED(!isA(n, PT_CLASS_FUNC_CALL, is))) return false; + if (is) { + zval *name = read(pt_svi_func_call_name_site, n, PT_LC("name")); + if (UNEXPECTED(name == NULL)) return false; + bool isName; + if (UNEXPECTED(!isA(name, PT_CLASS_NAME, isName))) return false; + if (isName) { + zv::Val nameHold = zv::Val::copyOf(zv::Ref(name)); + zv::Val lower = pt_call_method_cached(pt_svi_to_lower_string_site, Z_OBJ_P(nameHold.raw()), PT_LC("tolowerstring"), 0, NULL); + if (UNEXPECTED(lower.isUndef())) return false; + if (lower.ref().stringEquals("extract") || lower.ref().stringEquals("parse_str")) { + opaque = true; + return true; + } + } + } + if (UNEXPECTED(!isA(n, PT_CLASS_ASSIGN_REF_EXPR, is))) return false; + if (is) { + zval *var = read(pt_svi_assign_var_site, n, PT_LC("var")); + if (UNEXPECTED(var == NULL)) return false; + zv::Val varHold = zv::Val::copyOf(zv::Ref(var)); + if (UNEXPECTED(!collectRootNames(varHold.raw(), excludedNames))) return false; + zval *expr = read(pt_svi_assign_expr_site, n, PT_LC("expr")); + if (UNEXPECTED(expr == NULL)) return false; + zv::Val exprHold = zv::Val::copyOf(zv::Ref(expr)); + if (UNEXPECTED(!collectRootNames(exprHold.raw(), excludedNames))) return false; + } + if (UNEXPECTED(!isA(n, PT_CLASS_GLOBAL_STMT, is))) return false; + if (is) { + zval *vars = read(pt_svi_global_vars_site, n, PT_LC("vars")); + if (UNEXPECTED(vars == NULL)) return false; + if (Z_TYPE_P(vars) == IS_ARRAY) { + zv::Val varsHold = zv::Val::copyOf(zv::Ref(vars)); + for (auto entry : zv::ArrRef(varsHold.raw())) { + if (UNEXPECTED(!collectRootNames(entry.value().deref().raw(), excludedNames))) return false; + } + } + } + if (UNEXPECTED(!isA(n, PT_CLASS_STATIC_STMT, is))) return false; + if (is) { + bool varTag; + if (UNEXPECTED(!hasVarTag(n, varTag))) return false; + if (!varTag) { + zval *vars = read(pt_svi_static_vars_site, n, PT_LC("vars")); + if (UNEXPECTED(vars == NULL)) return false; + if (Z_TYPE_P(vars) == IS_ARRAY) { + zv::Val varsHold = zv::Val::copyOf(zv::Ref(vars)); + for (auto entry : zv::ArrRef(varsHold.raw())) { + zval *var = read(pt_svi_static_var_var_site, entry.value().deref().raw(), PT_LC("var")); + if (UNEXPECTED(var == NULL)) return false; + zv::Arr candidate = zv::Arr::create(2); + candidate.push(zv::Ref(var)); + candidate.push(zv::Val::integer(index)); + candidates.push(std::move(candidate)); + } + } + } + } + return true; +} + +/* Mirrors the private static scanSites(); UNDEF = pending exception */ +zv::Val scanSites(zval *stmts) +{ + zv::Arr candidates = zv::Arr::empty(); + zv::Arr excludedNames = zv::Arr::create(0); + if (Z_TYPE_P(stmts) == IS_ARRAY) { + for (auto stmtEntry : zv::ArrRef(stmts)) { + zend_long index = stmtEntry.stringKeyOrNull() == NULL ? (zend_long) stmtEntry.indexKey() : 0; + zv::Arr stack = zv::Arr::create(0); + stack.push(zv::Val::copyOf(stmtEntry.value().deref())); + for (;;) { + zv::Val node = popNode(stack); + if (node.isUndef()) break; + bool skip, opaque; + bool ok = true; + pt_engine_with_stack([&]() { ok = scanNode(node.raw(), index, candidates, excludedNames, skip, opaque); }); + if (UNEXPECTED(!ok)) return zv::Val(); + if (opaque) return zv::Val(zv::Arr::empty()); + if (skip) continue; + if (UNEXPECTED(!pushSubNodes(node.raw(), stack))) return zv::Val(); + } + } + } + + zv::Arr sites = zv::Arr::empty(); + for (auto entry : zv::ArrRef(candidates.raw())) { + zval *candidate = entry.value().raw(); + zval *var = zend_hash_index_find(Z_ARRVAL_P(candidate), 0); + zval *index = zend_hash_index_find(Z_ARRVAL_P(candidate), 1); + zval *name = read(pt_svi_variable_name_site, var, PT_LC("name")); + if (UNEXPECTED(name == NULL)) return zv::Val(); + if (Z_TYPE_P(name) != IS_STRING || zend_symtable_exists(excludedNames.table(), Z_STR_P(name))) continue; + zv::Arr site = zv::Arr::create(3); + site.push(zv::Ref(var)); + site.push(zv::Ref(index)); + site.push(zv::Ref(name)); + sites.push(std::move(site)); + } + return zv::Val(std::move(sites)); +} + +} // namespace + +namespace phpstanturbo { + +/* Mirrors PHPStan\Analyser\Generics\StaticVariableInference; UNDEF / false = + * pending exception. */ +class StaticVariableInference +{ +public: + explicit StaticVariableInference(zend_object *self) : self(self) {} + + /* the constructor body: the promoted property */ + void construct(bool enabled) + { + zv::ObjRef(self).propAtWrite(slots::enabled, zv::Val::boolean(enabled)); + Z_PROP_FLAG_P(OBJ_PROP_NUM(self, slots::enabled)) = 0; + } + + /* Mirrors getSites() */ + zv::Val getSites(zval *functionLike, zval *stmts) const + { + if (Z_TYPE_P(OBJ_PROP_NUM(self, slots::enabled)) != IS_TRUE) return zv::Val(zv::Arr::empty()); + + zv::Val cached = pt_engine_node_get_attribute(Z_OBJ_P(functionLike), ZSTR_VAL(pt_svi_sites_attribute), ZSTR_LEN(pt_svi_sites_attribute)); + if (UNEXPECTED(cached.isUndef())) return zv::Val(); + if (!cached.isNull()) return cached; + + zv::Val sites = scanSites(stmts); + if (UNEXPECTED(sites.isUndef())) return zv::Val(); + if (UNEXPECTED(!pt_engine_node_set_attribute(Z_OBJ_P(functionLike), ZSTR_VAL(pt_svi_sites_attribute), ZSTR_LEN(pt_svi_sites_attribute), sites.raw()))) return zv::Val(); + return sites; + } + + /* Mirrors isInferred(); false = pending exception */ + [[nodiscard]] bool isInferred(zval *scope, zval *var, bool &out) const + { + out = false; + zv::Val frame = pt_mutating_scope_get_current_template_argument_frame(Z_OBJ_P(scope)); + if (UNEXPECTED(frame.isUndef())) return false; + if (frame.isNull()) return true; + zv::Val body = pt_template_argument_frame_get_closure_signature_body(frame.raw()); + if (UNEXPECTED(body.isUndef())) return false; + if (body.isNull()) return true; + zv::Val stmts = pt_template_argument_frame_get_closure_signature_stmts(frame.raw()); + if (UNEXPECTED(stmts.isUndef())) return false; + zv::Val sites = getSites(body.raw(), stmts.raw()); + if (UNEXPECTED(sites.isUndef())) return false; + for (auto entry : zv::ArrRef(sites.raw())) { + zval *site = zend_hash_index_find(Z_ARRVAL_P(entry.value().deref().raw()), 0); + if (site != NULL && Z_TYPE_P(site) == IS_OBJECT && Z_OBJ_P(site) == Z_OBJ_P(var)) { + out = true; + return true; + } + } + return true; + } + + /* Mirrors getResolvedTypes() */ + static zv::Val getResolvedTypes(zval *scope, zval *var) + { + zv::Val frame = pt_mutating_scope_get_current_template_argument_frame(Z_OBJ_P(scope)); + if (UNEXPECTED(frame.isUndef())) return zv::Val(); + if (frame.isNull()) return zv::Val::null(); + return pt_template_argument_frame_get_static_variable_types(frame.raw(), var); + } + +private: + zend_object *self; +}; + +} // namespace phpstanturbo + +using phpstanturbo::StaticVariableInference; + +/* {{{ direct entries (support.h): the native bodies for the native service, + * the methods otherwise */ + +namespace { + +inline bool isNative(zval *inference) +{ + return EXPECTED(Z_OBJCE_P(inference) == pt_ce_static_variable_inference); +} + +} // namespace + +zv::Val pt_static_variable_inference_get_sites(zval *inference, zval *functionLike, zval *stmts) +{ + if (isNative(inference)) return StaticVariableInference(Z_OBJ_P(inference)).getSites(functionLike, stmts); + zv::Args argv{functionLike, stmts}; + return pt_type_call(Z_OBJ_P(inference), PT_LC("getsites"), 2, argv); +} + +bool pt_static_variable_inference_is_inferred(zval *inference, zval *scope, zval *var, bool &out) +{ + if (isNative(inference)) return StaticVariableInference(Z_OBJ_P(inference)).isInferred(scope, var, out); + zv::Args argv{scope, var}; + zv::Val result = pt_type_call(Z_OBJ_P(inference), PT_LC("isinferred"), 2, argv); + if (UNEXPECTED(result.isUndef())) return false; + out = zend_is_true(result.raw()); + return true; +} + +zv::Val pt_static_variable_inference_get_resolved_types(zval *inference, zval *scope, zval *var) +{ + if (isNative(inference)) return StaticVariableInference::getResolvedTypes(scope, var); + zv::Args argv{scope, var}; + return pt_type_call(Z_OBJ_P(inference), PT_LC("getresolvedtypes"), 2, argv); +} + +/* }}} */ + +/* {{{ engine ABI glue: parameter parsing + registration */ + +#include "reg.h" + +PT_MINIT_REGISTRATION(pt_register_static_variable_inference) +{ + pt_svi_sites_attribute = zend_string_init_interned(PT_LC("staticVariableInferenceSites"), 1); + + reg::Class cls("PHPStan\\Analyser\\Generics\\StaticVariableInference"); + ptdecl::StaticVariableInference::declareClass(cls); + ptdecl::StaticVariableInference::declareProperties(cls); + cls.privateClassConstantString("SITES_ATTRIBUTE", "staticVariableInferenceSites"); + + /* the real parameter types: the DI container autowires the service by + * reflecting the constructor */ + cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { + bool enabled; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_BOOL(enabled) + ZEND_PARSE_PARAMETERS_END(); + StaticVariableInference(Z_OBJ_P(ZEND_THIS)).construct(enabled); + }); + + cls.method(sigs::getSites, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *functionLike, *stmts; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT(functionLike) + Z_PARAM_ARRAY(stmts) + ZEND_PARSE_PARAMETERS_END(); + PT_RETURN_VAL(StaticVariableInference(Z_OBJ_P(ZEND_THIS)).getSites(functionLike, stmts)); + }); + + cls.method(sigs::isInferred, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *scope, *var; + if (!zp::parse(execute_data, scope, var)) RETURN_THROWS(); + bool out; + if (UNEXPECTED(!StaticVariableInference(Z_OBJ_P(ZEND_THIS)).isInferred(scope, var, out))) RETURN_THROWS(); + RETURN_BOOL(out); + }); + + cls.method(sigs::getResolvedTypes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *scope, *var; + if (!zp::parse(execute_data, scope, var)) RETURN_THROWS(); + PT_RETURN_VAL(StaticVariableInference::getResolvedTypes(scope, var)); + }); + + cls.shadow(&pt_ce_static_variable_inference); +} + +/* }}} */ diff --git a/turbo-ext/src/TemplateArgumentFrame.cpp b/turbo-ext/src/TemplateArgumentFrame.cpp index 02e51695fa5..5647ac0f530 100644 --- a/turbo-ext/src/TemplateArgumentFrame.cpp +++ b/turbo-ext/src/TemplateArgumentFrame.cpp @@ -91,10 +91,10 @@ class TemplateArgumentFrame * [], private readonly ?Node $closureSignatureBody = null, private * readonly array $closureSignatureStmts = [], private readonly array * $settledClosureSites = [], private readonly bool $observingClosures = - * false, private readonly array $byRefSites = []); NULL for null / []. - * false = pending exception (a repeated construction modifies readonly - * properties) */ - [[nodiscard]] bool construct(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites) const + * false, private readonly array $byRefSites = [], private readonly array + * $staticVariableTypes = []); NULL for null / []. false = pending + * exception (a repeated construction modifies readonly properties) */ + [[nodiscard]] bool construct(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *staticVariableTypes) const { if (UNEXPECTED(Z_TYPE_P(OBJ_PROP_NUM(self, slots::parent)) != IS_UNDEF)) { zend_throw_error(NULL, "Cannot modify readonly property %s::$parent", ZSTR_VAL(self->ce->name)); @@ -145,9 +145,57 @@ class TemplateArgumentFrame ZVAL_EMPTY_ARRAY(&value); pt_write_slot(self, slots::byRefSites, &value); } + if (staticVariableTypes != NULL) { + pt_write_slot(self, slots::staticVariableTypes, staticVariableTypes); + } else { + ZVAL_EMPTY_ARRAY(&value); + pt_write_slot(self, slots::staticVariableTypes, &value); + } return true; } + /* Mirrors getStaticVariableTypes(): [phpdoc, native] or PHP null */ + zv::Val getStaticVariableTypes(zval *var) const + { + zval *types = pt_typed_slot(self, slots::staticVariableTypes, self->ce, "staticVariableTypes"); + if (UNEXPECTED(types == NULL)) return zv::Val(); + zval *found = zend_hash_index_find(Z_ARRVAL_P(types), Z_OBJ_HANDLE_P(var)); + if (found == NULL || Z_TYPE_P(found) != IS_ARRAY) return zv::Val::null(); + zval *node = zend_hash_index_find(Z_ARRVAL_P(found), 0); + if (node == NULL || Z_TYPE_P(node) != IS_OBJECT || Z_OBJ_P(node) != Z_OBJ_P(var)) return zv::Val::null(); + zval *type = zend_hash_index_find(Z_ARRVAL_P(found), 1); + zval *nativeType = zend_hash_index_find(Z_ARRVAL_P(found), 2); + if (UNEXPECTED(type == NULL || nativeType == NULL)) return zv::Val::null(); + zv::Arr pair = zv::Arr::create(2); + pair.push(zv::Ref(type)); + pair.push(zv::Ref(nativeType)); + return zv::Val(std::move(pair)); + } + + /* Mirrors withStaticVariableTypes(); UNDEF = pending exception */ + zv::Val withStaticVariableTypes(zval *staticVariableTypes, zval *statementIndexes) const + { + zval *parent = pt_typed_slot(self, slots::parent, self->ce, "parent"); + if (UNEXPECTED(parent == NULL)) return zv::Val(); + zval *resolutions = pt_typed_slot(self, slots::resolutions, self->ce, "resolutions"); + if (UNEXPECTED(resolutions == NULL)) return zv::Val(); + zval *indexes = pt_typed_slot(self, slots::siteStatementIndexes, self->ce, "siteStatementIndexes"); + if (UNEXPECTED(indexes == NULL)) return zv::Val(); + zv::Val mergedIndexes = arrayUnion(Z_ARRVAL_P(indexes), Z_ARRVAL_P(statementIndexes)); + zval *body = OBJ_PROP_NUM(self, slots::closureSignatureBody); + return create( + Z_TYPE_P(parent) == IS_NULL ? NULL : parent, + Z_TYPE_P(resolutions) == IS_NULL ? NULL : resolutions, + mergedIndexes.raw(), + Z_TYPE_P(body) == IS_NULL ? NULL : body, + OBJ_PROP_NUM(self, slots::closureSignatureStmts), + OBJ_PROP_NUM(self, slots::settledClosureSites), + Z_TYPE_P(OBJ_PROP_NUM(self, slots::observingClosures)) == IS_TRUE, + OBJ_PROP_NUM(self, slots::byRefSites), + staticVariableTypes + ); + } + /* Mirrors getByRefSiteMode(): the entry of this frame's byRefSites for * spl_object_id($site) holding that very node, then the parent's; the * interned 'local' / 'escaped', or PHP null. UNDEF = pending exception */ @@ -219,7 +267,8 @@ class TemplateArgumentFrame OBJ_PROP_NUM(self, slots::closureSignatureStmts), settledClosureSites, false, - byRefSites + byRefSites, + OBJ_PROP_NUM(self, slots::staticVariableTypes) ); } @@ -263,12 +312,12 @@ class TemplateArgumentFrame zval *closureSignatureStmts() const { return OBJ_PROP_NUM(self, slots::closureSignatureStmts); } /* new self(...); UNDEF = pending exception */ - static zv::Val create(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites) + static zv::Val create(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *staticVariableTypes = NULL) { zval object; if (UNEXPECTED(object_init_ex(&object, pt_ce_template_argument_frame) != SUCCESS)) return zv::Val(); zv::Val frame = zv::Val::adopt(object); - if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(frame.raw())).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites))) return zv::Val(); + if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(frame.raw())).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, staticVariableTypes))) return zv::Val(); return frame; } @@ -590,6 +639,19 @@ zv::Val pt_template_argument_frame_get_by_ref_site_mode(zval *frame, zval *site) return pt_type_call(Z_OBJ_P(frame), PT_LC("getbyrefsitemode"), 1, site); } +zv::Val pt_template_argument_frame_get_static_variable_types(zval *frame, zval *var) +{ + if (EXPECTED(Z_OBJCE_P(frame) == pt_ce_template_argument_frame)) return TemplateArgumentFrame(Z_OBJ_P(frame)).getStaticVariableTypes(var); + return pt_type_call(Z_OBJ_P(frame), PT_LC("getstaticvariabletypes"), 1, var); +} + +zv::Val pt_template_argument_frame_with_static_variable_types(zval *frame, zval *staticVariableTypes, zval *statementIndexes) +{ + if (EXPECTED(Z_OBJCE_P(frame) == pt_ce_template_argument_frame)) return TemplateArgumentFrame(Z_OBJ_P(frame)).withStaticVariableTypes(staticVariableTypes, statementIndexes); + zv::Args argv{staticVariableTypes, statementIndexes}; + return pt_type_call(Z_OBJ_P(frame), PT_LC("withstaticvariabletypes"), 2, argv); +} + zv::Val pt_template_argument_frame_get_local_by_ref_sites(zval *frame) { if (EXPECTED(Z_OBJCE_P(frame) == pt_ce_template_argument_frame)) return TemplateArgumentFrame(Z_OBJ_P(frame)).getLocalByRefSites(); @@ -653,9 +715,9 @@ PT_MINIT_REGISTRATION(pt_register_template_argument_frame) }); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *parent, *resolutions = NULL, *siteStatementIndexes = NULL, *closureSignatureBody = NULL, *closureSignatureStmts = NULL, *settledClosureSites = NULL, *byRefSites = NULL; + zval *parent, *resolutions = NULL, *siteStatementIndexes = NULL, *closureSignatureBody = NULL, *closureSignatureStmts = NULL, *settledClosureSites = NULL, *byRefSites = NULL, *staticVariableTypes = NULL; bool observingClosures = false; - ZEND_PARSE_PARAMETERS_START(1, 8) + ZEND_PARSE_PARAMETERS_START(1, 9) Z_PARAM_OBJECT_OR_NULL(parent) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(resolutions) @@ -665,8 +727,9 @@ PT_MINIT_REGISTRATION(pt_register_template_argument_frame) Z_PARAM_ARRAY(settledClosureSites) Z_PARAM_BOOL(observingClosures) Z_PARAM_ARRAY(byRefSites) + Z_PARAM_ARRAY(staticVariableTypes) ZEND_PARSE_PARAMETERS_END(); - if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites))) RETURN_THROWS(); + if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, staticVariableTypes))) RETURN_THROWS(); }); cls.method(sigs::withObservedClosures, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -685,6 +748,17 @@ PT_MINIT_REGISTRATION(pt_register_template_argument_frame) cls.method<&TemplateArgumentFrame::getLocalByRefSites>(sigs::getLocalByRefSites); + cls.method<&TemplateArgumentFrame::getStaticVariableTypes, zp::Obj>(sigs::getStaticVariableTypes); + + cls.method(sigs::withStaticVariableTypes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *staticVariableTypes, *statementIndexes; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ARRAY(staticVariableTypes) + Z_PARAM_ARRAY(statementIndexes) + ZEND_PARSE_PARAMETERS_END(); + PT_RETURN_VAL(TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).withStaticVariableTypes(staticVariableTypes, statementIndexes)); + }); + cls.method(sigs::isSettledClosureSite, [](INTERNAL_FUNCTION_PARAMETERS) { zval *site; ZEND_PARSE_PARAMETERS_START(1, 1) diff --git a/turbo-ext/src/generated/StatementsHandler.h b/turbo-ext/src/generated/StatementsHandler.h index af90a20b11d..0e0cdd2fcf3 100644 --- a/turbo-ext/src/generated/StatementsHandler.h +++ b/turbo-ext/src/generated/StatementsHandler.h @@ -15,7 +15,8 @@ inline constexpr uint32_t fileTypeMapper = 1; inline constexpr uint32_t templateArgumentObserver = 2; inline constexpr uint32_t templateArgumentResolver = 3; inline constexpr uint32_t container = 4; -inline constexpr uint32_t unresolvedTemplateArguments = 5; +inline constexpr uint32_t staticVariableInference = 5; +inline constexpr uint32_t unresolvedTemplateArguments = 6; } // namespace slot inline void declareClass(reg::Class &cls) @@ -31,6 +32,7 @@ inline void declareProperties(reg::Class &cls) cls.property("templateArgumentObserver", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\Generics\\TemplateArgumentObserver"); cls.property("templateArgumentResolver", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\Generics\\TemplateArgumentResolver"); cls.property("container", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\DependencyInjection\\Container"); + cls.property("staticVariableInference", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\Generics\\StaticVariableInference"); cls.property("unresolvedTemplateArguments", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); } @@ -45,230 +47,273 @@ inline constexpr char strings[] = "PHPStan\\Analyser\\Generics\\TemplateArgumentResolver\0" /* 144 */ "container\0" /* 195 */ "PHPStan\\DependencyInjection\\Container\0" /* 205 */ - "unresolvedTemplateArguments\0" /* 243 */ - "__construct\0" /* 271 */ - "nodeScopeResolver\0" /* 283 */ - "PHPStan\\Analyser\\NodeScopeResolver\0" /* 301 */ - "nodes\0" /* 336 */ - "scope\0" /* 342 */ - "PHPStan\\Analyser\\MutatingScope\0" /* 348 */ - "expressionResultStorage\0" /* 379 */ - "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 403 */ - "nodeCallback\0" /* 444 */ - "\0" /* 457 */ - "processNodesWithStorage\0" /* 458 */ - "parentNode\0" /* 482 */ - "PhpParser\\Node\0" /* 493 */ - "bodyStmts\0" /* 508 */ - "storage\0" /* 518 */ - "context\0" /* 526 */ - "PHPStan\\Analyser\\StatementContext\0" /* 534 */ - "gotoNameMatcher\0" /* 568 */ - "Closure\0" /* 584 */ - "mergeBodyScopeEachIteration\0" /* 592 */ - "resolveBackwardGotoScope\0" /* 620 */ - "labelName\0" /* 645 */ - "alreadyTerminated\0" /* 655 */ - "exitPoints\0" /* 673 */ - "mergeForwardGotoExitPoints\0" /* 684 */ - "nextStmts\0" /* 711 */ - "processUnreachableStatement\0" /* 721 */ - "earlyBinding\0" /* 749 */ - "getNextUnreachableStatements\0" /* 762 */ - "stmts\0" /* 791 */ - "PHPStan\\Analyser\\InternalStatementResult\0" /* 797 */ - "doProcessStmtNodes\0" /* 838 */ - "i\0" /* 857 */ - "stmt\0" /* 859 */ - "PhpParser\\Node\\Stmt\0" /* 864 */ - "state\0" /* 884 */ - "PHPStan\\Analyser\\StatementListWalkState\0" /* 890 */ - "shouldCheckLastStatement\0" /* 930 */ - "processStatementStep\0" /* 955 */ - "PHPStan\\Analyser\\ExpressionResult\0" /* 976 */ - "readEndStatementExprResult\0" /* 1010 */ - "processBodyStmtNodesTwoPass\0" /* 1037 */ - "PHPStan\\Analyser\\ClosureProcessor\0" /* 1065 */ - "getClosureProcessor\0" /* 1099 */ - "frame\0" /* 1119 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1125 */ - "processDeferredByRefClosureBodies\0" /* 1173 */ - "entries\0" /* 1207 */ - "statementStartTokenPositions\0" /* 1215 */ - "observeClosureSignatures\0" /* 1244 */ - "recordedEntry\0" /* 1269 */ - "recordedExit\0" /* 1283 */ - "withRecordedConstraints\0" /* 1296 */ - "containsLabels\0" /* 1320 */ - "from\0" /* 1335 */ - "to\0" /* 1340 */ - "appendRecordedStatementResults\0" /* 1343 */ - "PHPStan\\Analyser\\VariableFlow\0" /* 1374 */ - "getVariableMentionFlow\0" /* 1404 */ - "variableNames\0" /* 1427 */ - "statementMentionsAnyVariable\0" /* 1441 */ - "node\0" /* 1470 */ - "names\0" /* 1475 */ - "mentionsEverything\0" /* 1481 */ - "collectMentionedVariables\0" /* 1500 */ - "statement\0" /* 1526 */ - "getOverridingThrowPoints\0" /* 1536 */ - "defaultExpr\0" /* 1561 */ - "PhpParser\\Node\\Expr\0" /* 1573 */ - "processStmtVarAnnotation\0" /* 1593 */ - "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 1618 */ - "findSingleVariableLessVarTag\0" /* 1644 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1673 */ - "emitVarTagChangedNode"; /* 1727 */ + "staticVariableInference\0" /* 243 */ + "PHPStan\\Analyser\\Generics\\StaticVariableInference\0" /* 267 */ + "unresolvedTemplateArguments\0" /* 317 */ + "__construct\0" /* 345 */ + "nodeScopeResolver\0" /* 357 */ + "PHPStan\\Analyser\\NodeScopeResolver\0" /* 375 */ + "nodes\0" /* 410 */ + "scope\0" /* 416 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 422 */ + "expressionResultStorage\0" /* 453 */ + "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 477 */ + "nodeCallback\0" /* 518 */ + "\0" /* 531 */ + "processNodesWithStorage\0" /* 532 */ + "parentNode\0" /* 556 */ + "PhpParser\\Node\0" /* 567 */ + "bodyStmts\0" /* 582 */ + "storage\0" /* 592 */ + "context\0" /* 600 */ + "PHPStan\\Analyser\\StatementContext\0" /* 608 */ + "gotoNameMatcher\0" /* 642 */ + "Closure\0" /* 658 */ + "mergeBodyScopeEachIteration\0" /* 666 */ + "resolveBackwardGotoScope\0" /* 694 */ + "labelName\0" /* 719 */ + "alreadyTerminated\0" /* 729 */ + "exitPoints\0" /* 747 */ + "mergeForwardGotoExitPoints\0" /* 758 */ + "nextStmts\0" /* 785 */ + "processUnreachableStatement\0" /* 795 */ + "earlyBinding\0" /* 823 */ + "getNextUnreachableStatements\0" /* 836 */ + "stmts\0" /* 865 */ + "PHPStan\\Analyser\\InternalStatementResult\0" /* 871 */ + "doProcessStmtNodes\0" /* 912 */ + "i\0" /* 931 */ + "stmt\0" /* 933 */ + "PhpParser\\Node\\Stmt\0" /* 938 */ + "state\0" /* 958 */ + "PHPStan\\Analyser\\StatementListWalkState\0" /* 964 */ + "shouldCheckLastStatement\0" /* 1004 */ + "processStatementStep\0" /* 1029 */ + "PHPStan\\Analyser\\ExpressionResult\0" /* 1050 */ + "readEndStatementExprResult\0" /* 1084 */ + "processBodyStmtNodesTwoPass\0" /* 1111 */ + "staticSites\0" /* 1139 */ + "staticStatementIndexes\0" /* 1151 */ + "parentFrame\0" /* 1174 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1186 */ + "entries\0" /* 1234 */ + "recording\0" /* 1242 */ + "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1252 */ + "inferStaticVariableTypes\0" /* 1291 */ + "bodyScope\0" /* 1316 */ + "names\0" /* 1326 */ + "endState\0" /* 1332 */ + "moreScopes\0" /* 1341 */ + "collectStaticVariableTypes\0" /* 1352 */ + "types\0" /* 1379 */ + "walkTypes\0" /* 1385 */ + "generalize\0" /* 1395 */ + "joinStaticVariableTypes\0" /* 1406 */ + "PHPStan\\Analyser\\ClosureProcessor\0" /* 1430 */ + "getClosureProcessor\0" /* 1464 */ + "frame\0" /* 1484 */ + "processDeferredByRefClosureBodies\0" /* 1490 */ + "statementStartTokenPositions\0" /* 1524 */ + "observeClosureSignatures\0" /* 1553 */ + "recordedEntry\0" /* 1578 */ + "recordedExit\0" /* 1592 */ + "withRecordedConstraints\0" /* 1605 */ + "containsLabels\0" /* 1629 */ + "from\0" /* 1644 */ + "to\0" /* 1649 */ + "appendRecordedStatementResults\0" /* 1652 */ + "PHPStan\\Analyser\\VariableFlow\0" /* 1683 */ + "getVariableMentionFlow\0" /* 1713 */ + "variableNames\0" /* 1736 */ + "statementMentionsAnyVariable\0" /* 1750 */ + "node\0" /* 1779 */ + "mentionsEverything\0" /* 1784 */ + "collectMentionedVariables\0" /* 1803 */ + "statement\0" /* 1829 */ + "getOverridingThrowPoints\0" /* 1839 */ + "defaultExpr\0" /* 1864 */ + "PhpParser\\Node\\Expr\0" /* 1876 */ + "processStmtVarAnnotation\0" /* 1896 */ + "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 1921 */ + "findSingleVariableLessVarTag\0" /* 1947 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1976 */ + "emitVarTagChangedNode"; /* 2030 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 15), /* __construct $fileTypeMapper */ reg::packed(43, 0, 68), /* __construct $templateArgumentObserver */ reg::packed(119, 0, 144), /* __construct $templateArgumentResolver */ reg::packed(195, 0, 205), /* __construct $container */ - reg::packed(243, MAY_BE_BOOL), /* __construct $unresolvedTemplateArguments */ - reg::packed(283, 0, 301), /* processNodesWithStorage $nodeScopeResolver */ - reg::packed(336, MAY_BE_ARRAY), /* processNodesWithStorage $nodes */ - reg::packed(342, 0, 348), /* processNodesWithStorage $scope */ - reg::packed(379, 0, 403), /* processNodesWithStorage $expressionResultStorage */ - reg::packed(444, MAY_BE_CALLABLE), /* processNodesWithStorage $nodeCallback */ - reg::packed(457, MAY_BE_VOID), /* processNodesWithStorage return */ - reg::packed(283, 0, 301), /* resolveBackwardGotoScope $nodeScopeResolver */ - reg::packed(482, 0, 493), /* resolveBackwardGotoScope $parentNode */ - reg::packed(508, MAY_BE_ARRAY), /* resolveBackwardGotoScope $bodyStmts */ - reg::packed(342, 0, 348), /* resolveBackwardGotoScope $scope */ - reg::packed(518, 0, 403), /* resolveBackwardGotoScope $storage */ - reg::packed(526, 0, 534), /* resolveBackwardGotoScope $context */ - reg::packed(568, 0, 584), /* resolveBackwardGotoScope $gotoNameMatcher */ - reg::packed(592, MAY_BE_BOOL), /* resolveBackwardGotoScope $mergeBodyScopeEachIteration */ - reg::packed(457, 0, 348), /* resolveBackwardGotoScope return */ - reg::packed(645, MAY_BE_STRING), /* mergeForwardGotoExitPoints $labelName */ - reg::packed(342, 0, 348), /* mergeForwardGotoExitPoints $scope */ - reg::packed(655, MAY_BE_BOOL), /* mergeForwardGotoExitPoints $alreadyTerminated */ - reg::packed(673, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints $exitPoints */ - reg::packed(457, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints return */ - reg::packed(283, 0, 301), /* processUnreachableStatement $nodeScopeResolver */ - reg::packed(711, MAY_BE_ARRAY), /* processUnreachableStatement $nextStmts */ - reg::packed(342, 0, 348), /* processUnreachableStatement $scope */ - reg::packed(518, 0, 403), /* processUnreachableStatement $storage */ - reg::packed(444, MAY_BE_CALLABLE), /* processUnreachableStatement $nodeCallback */ - reg::packed(457, MAY_BE_VOID), /* processUnreachableStatement return */ - reg::packed(336, MAY_BE_ARRAY), /* getNextUnreachableStatements $nodes */ - reg::packed(749, MAY_BE_BOOL), /* getNextUnreachableStatements $earlyBinding */ - reg::packed(457, MAY_BE_ARRAY), /* getNextUnreachableStatements return */ - reg::packed(283, 0, 301), /* doProcessStmtNodes $nodeScopeResolver */ - reg::packed(482, 0, 493), /* doProcessStmtNodes $parentNode */ - reg::packed(791, MAY_BE_ARRAY), /* doProcessStmtNodes $stmts */ - reg::packed(342, 0, 348), /* doProcessStmtNodes $scope */ - reg::packed(518, 0, 403), /* doProcessStmtNodes $storage */ - reg::packed(444, MAY_BE_CALLABLE), /* doProcessStmtNodes $nodeCallback */ - reg::packed(526, 0, 534), /* doProcessStmtNodes $context */ - reg::packed(457, 0, 797), /* doProcessStmtNodes return */ - reg::packed(283, 0, 301), /* processStatementStep $nodeScopeResolver */ - reg::packed(482, 0, 493), /* processStatementStep $parentNode */ - reg::packed(791, MAY_BE_ARRAY), /* processStatementStep $stmts */ - reg::packed(857, MAY_BE_LONG), /* processStatementStep $i */ - reg::packed(859, 0, 864), /* processStatementStep $stmt */ - reg::packed(884, 0, 890), /* processStatementStep $state */ - reg::packed(518, 0, 403), /* processStatementStep $storage */ - reg::packed(444, MAY_BE_CALLABLE), /* processStatementStep $nodeCallback */ - reg::packed(526, 0, 534), /* processStatementStep $context */ - reg::packed(930, MAY_BE_BOOL), /* processStatementStep $shouldCheckLastStatement */ - reg::packed(457, MAY_BE_VOID), /* processStatementStep return */ - reg::packed(859, 0, 864), /* readEndStatementExprResult $stmt */ - reg::packed(518, 0, 403), /* readEndStatementExprResult $storage */ - reg::packed(457, MAY_BE_NULL, 976), /* readEndStatementExprResult return */ - reg::packed(283, 0, 301), /* processBodyStmtNodesTwoPass $nodeScopeResolver */ - reg::packed(482, 0, 493), /* processBodyStmtNodesTwoPass $parentNode */ - reg::packed(791, MAY_BE_ARRAY), /* processBodyStmtNodesTwoPass $stmts */ - reg::packed(342, 0, 348), /* processBodyStmtNodesTwoPass $scope */ - reg::packed(518, 0, 403), /* processBodyStmtNodesTwoPass $storage */ - reg::packed(444, MAY_BE_CALLABLE), /* processBodyStmtNodesTwoPass $nodeCallback */ - reg::packed(526, 0, 534), /* processBodyStmtNodesTwoPass $context */ - reg::packed(457, 0, 797), /* processBodyStmtNodesTwoPass return */ - reg::packed(457, 0, 1065), /* getClosureProcessor return */ - reg::packed(283, 0, 301), /* processDeferredByRefClosureBodies $nodeScopeResolver */ - reg::packed(1119, 0, 1125), /* processDeferredByRefClosureBodies $frame */ - reg::packed(884, 0, 890), /* processDeferredByRefClosureBodies $state */ - reg::packed(518, 0, 403), /* processDeferredByRefClosureBodies $storage */ - reg::packed(444, MAY_BE_CALLABLE), /* processDeferredByRefClosureBodies $nodeCallback */ - reg::packed(457, MAY_BE_VOID), /* processDeferredByRefClosureBodies return */ - reg::packed(283, 0, 301), /* observeClosureSignatures $nodeScopeResolver */ - reg::packed(482, 0, 493), /* observeClosureSignatures $parentNode */ - reg::packed(791, MAY_BE_ARRAY), /* observeClosureSignatures $stmts */ - reg::packed(1119, 0, 1125), /* observeClosureSignatures $frame */ - reg::packed(1207, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ - reg::packed(518, 0, 403), /* observeClosureSignatures $storage */ - reg::packed(526, 0, 534), /* observeClosureSignatures $context */ - reg::packed(1215, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ - reg::packed(457, 0, 1125), /* observeClosureSignatures return */ - reg::packed(342, 0, 348), /* withRecordedConstraints $scope */ - reg::packed(1269, 0, 348), /* withRecordedConstraints $recordedEntry */ - reg::packed(1283, 0, 348), /* withRecordedConstraints $recordedExit */ - reg::packed(457, 0, 348), /* withRecordedConstraints return */ - reg::packed(791, MAY_BE_ARRAY), /* containsLabels $stmts */ - reg::packed(457, MAY_BE_BOOL), /* containsLabels return */ - reg::packed(884, 0, 890), /* appendRecordedStatementResults $state */ - reg::packed(1335, 0, 890), /* appendRecordedStatementResults $from */ - reg::packed(1340, 0, 890), /* appendRecordedStatementResults $to */ - reg::packed(457, MAY_BE_VOID), /* appendRecordedStatementResults return */ - reg::packed(859, 0, 864), /* getVariableMentionFlow $stmt */ - reg::packed(457, MAY_BE_NULL, 1374), /* getVariableMentionFlow return */ - reg::packed(859, 0, 864), /* statementMentionsAnyVariable $stmt */ - reg::packed(1427, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ - reg::packed(457, MAY_BE_BOOL), /* statementMentionsAnyVariable return */ - reg::packed(1470, 0, 493), /* collectMentionedVariables $node */ - reg::packed(1475, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ - reg::packed(1481, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ - reg::packed(457, MAY_BE_VOID), /* collectMentionedVariables return */ - reg::packed(1526, 0, 864), /* getOverridingThrowPoints $statement */ - reg::packed(342, 0, 348), /* getOverridingThrowPoints $scope */ - reg::packed(457, MAY_BE_NULL | MAY_BE_ARRAY), /* getOverridingThrowPoints return */ - reg::packed(283, 0, 301), /* processStmtVarAnnotation $nodeScopeResolver */ - reg::packed(342, 0, 348), /* processStmtVarAnnotation $scope */ - reg::packed(518, 0, 403), /* processStmtVarAnnotation $storage */ - reg::packed(859, 0, 864), /* processStmtVarAnnotation $stmt */ - reg::packed(1561, MAY_BE_NULL, 1573), /* processStmtVarAnnotation $defaultExpr */ - reg::packed(444, MAY_BE_CALLABLE), /* processStmtVarAnnotation $nodeCallback */ - reg::packed(457, 0, 348), /* processStmtVarAnnotation return */ - reg::packed(342, 0, 348), /* findSingleVariableLessVarTag $scope */ - reg::packed(859, 0, 864), /* findSingleVariableLessVarTag $stmt */ - reg::packed(457, MAY_BE_NULL, 1618), /* findSingleVariableLessVarTag return */ - reg::packed(283, 0, 301), /* emitVarTagChangedNode $nodeScopeResolver */ - reg::packed(342, 0, 348), /* emitVarTagChangedNode $scope */ - reg::packed(518, 0, 403), /* emitVarTagChangedNode $storage */ - reg::packed(859, 0, 864), /* emitVarTagChangedNode $stmt */ - reg::packed(1561, 0, 1573), /* emitVarTagChangedNode $defaultExpr */ - reg::packed(444, MAY_BE_CALLABLE), /* emitVarTagChangedNode $nodeCallback */ - reg::packed(457, 0, 1673), /* emitVarTagChangedNode return */ + reg::packed(243, 0, 267), /* __construct $staticVariableInference */ + reg::packed(317, MAY_BE_BOOL), /* __construct $unresolvedTemplateArguments */ + reg::packed(357, 0, 375), /* processNodesWithStorage $nodeScopeResolver */ + reg::packed(410, MAY_BE_ARRAY), /* processNodesWithStorage $nodes */ + reg::packed(416, 0, 422), /* processNodesWithStorage $scope */ + reg::packed(453, 0, 477), /* processNodesWithStorage $expressionResultStorage */ + reg::packed(518, MAY_BE_CALLABLE), /* processNodesWithStorage $nodeCallback */ + reg::packed(531, MAY_BE_VOID), /* processNodesWithStorage return */ + reg::packed(357, 0, 375), /* resolveBackwardGotoScope $nodeScopeResolver */ + reg::packed(556, 0, 567), /* resolveBackwardGotoScope $parentNode */ + reg::packed(582, MAY_BE_ARRAY), /* resolveBackwardGotoScope $bodyStmts */ + reg::packed(416, 0, 422), /* resolveBackwardGotoScope $scope */ + reg::packed(592, 0, 477), /* resolveBackwardGotoScope $storage */ + reg::packed(600, 0, 608), /* resolveBackwardGotoScope $context */ + reg::packed(642, 0, 658), /* resolveBackwardGotoScope $gotoNameMatcher */ + reg::packed(666, MAY_BE_BOOL), /* resolveBackwardGotoScope $mergeBodyScopeEachIteration */ + reg::packed(531, 0, 422), /* resolveBackwardGotoScope return */ + reg::packed(719, MAY_BE_STRING), /* mergeForwardGotoExitPoints $labelName */ + reg::packed(416, 0, 422), /* mergeForwardGotoExitPoints $scope */ + reg::packed(729, MAY_BE_BOOL), /* mergeForwardGotoExitPoints $alreadyTerminated */ + reg::packed(747, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints $exitPoints */ + reg::packed(531, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints return */ + reg::packed(357, 0, 375), /* processUnreachableStatement $nodeScopeResolver */ + reg::packed(785, MAY_BE_ARRAY), /* processUnreachableStatement $nextStmts */ + reg::packed(416, 0, 422), /* processUnreachableStatement $scope */ + reg::packed(592, 0, 477), /* processUnreachableStatement $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* processUnreachableStatement $nodeCallback */ + reg::packed(531, MAY_BE_VOID), /* processUnreachableStatement return */ + reg::packed(410, MAY_BE_ARRAY), /* getNextUnreachableStatements $nodes */ + reg::packed(823, MAY_BE_BOOL), /* getNextUnreachableStatements $earlyBinding */ + reg::packed(531, MAY_BE_ARRAY), /* getNextUnreachableStatements return */ + reg::packed(357, 0, 375), /* doProcessStmtNodes $nodeScopeResolver */ + reg::packed(556, 0, 567), /* doProcessStmtNodes $parentNode */ + reg::packed(865, MAY_BE_ARRAY), /* doProcessStmtNodes $stmts */ + reg::packed(416, 0, 422), /* doProcessStmtNodes $scope */ + reg::packed(592, 0, 477), /* doProcessStmtNodes $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* doProcessStmtNodes $nodeCallback */ + reg::packed(600, 0, 608), /* doProcessStmtNodes $context */ + reg::packed(531, 0, 871), /* doProcessStmtNodes return */ + reg::packed(357, 0, 375), /* processStatementStep $nodeScopeResolver */ + reg::packed(556, 0, 567), /* processStatementStep $parentNode */ + reg::packed(865, MAY_BE_ARRAY), /* processStatementStep $stmts */ + reg::packed(931, MAY_BE_LONG), /* processStatementStep $i */ + reg::packed(933, 0, 938), /* processStatementStep $stmt */ + reg::packed(958, 0, 964), /* processStatementStep $state */ + reg::packed(592, 0, 477), /* processStatementStep $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* processStatementStep $nodeCallback */ + reg::packed(600, 0, 608), /* processStatementStep $context */ + reg::packed(1004, MAY_BE_BOOL), /* processStatementStep $shouldCheckLastStatement */ + reg::packed(531, MAY_BE_VOID), /* processStatementStep return */ + reg::packed(933, 0, 938), /* readEndStatementExprResult $stmt */ + reg::packed(592, 0, 477), /* readEndStatementExprResult $storage */ + reg::packed(531, MAY_BE_NULL, 1050), /* readEndStatementExprResult return */ + reg::packed(357, 0, 375), /* processBodyStmtNodesTwoPass $nodeScopeResolver */ + reg::packed(556, 0, 567), /* processBodyStmtNodesTwoPass $parentNode */ + reg::packed(865, MAY_BE_ARRAY), /* processBodyStmtNodesTwoPass $stmts */ + reg::packed(416, 0, 422), /* processBodyStmtNodesTwoPass $scope */ + reg::packed(592, 0, 477), /* processBodyStmtNodesTwoPass $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* processBodyStmtNodesTwoPass $nodeCallback */ + reg::packed(600, 0, 608), /* processBodyStmtNodesTwoPass $context */ + reg::packed(531, 0, 871), /* processBodyStmtNodesTwoPass return */ + reg::packed(357, 0, 375), /* inferStaticVariableTypes $nodeScopeResolver */ + reg::packed(556, 0, 567), /* inferStaticVariableTypes $parentNode */ + reg::packed(865, MAY_BE_ARRAY), /* inferStaticVariableTypes $stmts */ + reg::packed(1139, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticSites */ + reg::packed(1151, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticStatementIndexes */ + reg::packed(1174, MAY_BE_NULL, 1186), /* inferStaticVariableTypes $parentFrame */ + reg::packed(1234, MAY_BE_ARRAY), /* inferStaticVariableTypes $entries */ + reg::packed(1242, 0, 1252), /* inferStaticVariableTypes $recording */ + reg::packed(592, 0, 477), /* inferStaticVariableTypes $storage */ + reg::packed(600, 0, 608), /* inferStaticVariableTypes $context */ + reg::packed(531, MAY_BE_ARRAY), /* inferStaticVariableTypes return */ + reg::packed(1316, 0, 422), /* collectStaticVariableTypes $bodyScope */ + reg::packed(1326, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ + reg::packed(1242, 0, 1252), /* collectStaticVariableTypes $recording */ + reg::packed(1332, 0, 964), /* collectStaticVariableTypes $endState */ + reg::packed(1341, MAY_BE_ARRAY), /* collectStaticVariableTypes $moreScopes */ + reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableTypes return */ + reg::packed(1316, 0, 422), /* joinStaticVariableTypes $bodyScope */ + reg::packed(1326, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ + reg::packed(1379, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ + reg::packed(1385, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ + reg::packed(1395, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ + reg::packed(531, MAY_BE_ARRAY), /* joinStaticVariableTypes return */ + reg::packed(531, 0, 1430), /* getClosureProcessor return */ + reg::packed(357, 0, 375), /* processDeferredByRefClosureBodies $nodeScopeResolver */ + reg::packed(1484, 0, 1186), /* processDeferredByRefClosureBodies $frame */ + reg::packed(958, 0, 964), /* processDeferredByRefClosureBodies $state */ + reg::packed(592, 0, 477), /* processDeferredByRefClosureBodies $storage */ + reg::packed(518, MAY_BE_CALLABLE), /* processDeferredByRefClosureBodies $nodeCallback */ + reg::packed(531, MAY_BE_VOID), /* processDeferredByRefClosureBodies return */ + reg::packed(357, 0, 375), /* observeClosureSignatures $nodeScopeResolver */ + reg::packed(556, 0, 567), /* observeClosureSignatures $parentNode */ + reg::packed(865, MAY_BE_ARRAY), /* observeClosureSignatures $stmts */ + reg::packed(1484, 0, 1186), /* observeClosureSignatures $frame */ + reg::packed(1234, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ + reg::packed(592, 0, 477), /* observeClosureSignatures $storage */ + reg::packed(600, 0, 608), /* observeClosureSignatures $context */ + reg::packed(1524, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ + reg::packed(531, 0, 1186), /* observeClosureSignatures return */ + reg::packed(416, 0, 422), /* withRecordedConstraints $scope */ + reg::packed(1578, 0, 422), /* withRecordedConstraints $recordedEntry */ + reg::packed(1592, 0, 422), /* withRecordedConstraints $recordedExit */ + reg::packed(531, 0, 422), /* withRecordedConstraints return */ + reg::packed(865, MAY_BE_ARRAY), /* containsLabels $stmts */ + reg::packed(531, MAY_BE_BOOL), /* containsLabels return */ + reg::packed(958, 0, 964), /* appendRecordedStatementResults $state */ + reg::packed(1644, 0, 964), /* appendRecordedStatementResults $from */ + reg::packed(1649, 0, 964), /* appendRecordedStatementResults $to */ + reg::packed(531, MAY_BE_VOID), /* appendRecordedStatementResults return */ + reg::packed(933, 0, 938), /* getVariableMentionFlow $stmt */ + reg::packed(531, MAY_BE_NULL, 1683), /* getVariableMentionFlow return */ + reg::packed(933, 0, 938), /* statementMentionsAnyVariable $stmt */ + reg::packed(1736, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ + reg::packed(531, MAY_BE_BOOL), /* statementMentionsAnyVariable return */ + reg::packed(1779, 0, 567), /* collectMentionedVariables $node */ + reg::packed(1326, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ + reg::packed(1784, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ + reg::packed(531, MAY_BE_VOID), /* collectMentionedVariables return */ + reg::packed(1829, 0, 938), /* getOverridingThrowPoints $statement */ + reg::packed(416, 0, 422), /* getOverridingThrowPoints $scope */ + reg::packed(531, MAY_BE_NULL | MAY_BE_ARRAY), /* getOverridingThrowPoints return */ + reg::packed(357, 0, 375), /* processStmtVarAnnotation $nodeScopeResolver */ + reg::packed(416, 0, 422), /* processStmtVarAnnotation $scope */ + reg::packed(592, 0, 477), /* processStmtVarAnnotation $storage */ + reg::packed(933, 0, 938), /* processStmtVarAnnotation $stmt */ + reg::packed(1864, MAY_BE_NULL, 1876), /* processStmtVarAnnotation $defaultExpr */ + reg::packed(518, MAY_BE_CALLABLE), /* processStmtVarAnnotation $nodeCallback */ + reg::packed(531, 0, 422), /* processStmtVarAnnotation return */ + reg::packed(416, 0, 422), /* findSingleVariableLessVarTag $scope */ + reg::packed(933, 0, 938), /* findSingleVariableLessVarTag $stmt */ + reg::packed(531, MAY_BE_NULL, 1921), /* findSingleVariableLessVarTag return */ + reg::packed(357, 0, 375), /* emitVarTagChangedNode $nodeScopeResolver */ + reg::packed(416, 0, 422), /* emitVarTagChangedNode $scope */ + reg::packed(592, 0, 477), /* emitVarTagChangedNode $storage */ + reg::packed(933, 0, 938), /* emitVarTagChangedNode $stmt */ + reg::packed(1864, 0, 1876), /* emitVarTagChangedNode $defaultExpr */ + reg::packed(518, MAY_BE_CALLABLE), /* emitVarTagChangedNode $nodeCallback */ + reg::packed(531, 0, 1976), /* emitVarTagChangedNode 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 = { { 271 /* __construct */, 5, 0, 5, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processNodesWithStorage = { { 458 /* processNodesWithStorage */, 5, 5, 5, 10, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveBackwardGotoScope = { { 620 /* resolveBackwardGotoScope */, 8, 11, 8, 19, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mergeForwardGotoExitPoints = { { 684 /* mergeForwardGotoExitPoints */, 4, 20, 4, 24, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processUnreachableStatement = { { 721 /* processUnreachableStatement */, 5, 25, 5, 30, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getNextUnreachableStatements = { { 762 /* getNextUnreachableStatements */, 2, 31, 2, 33, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig doProcessStmtNodes = { { 838 /* doProcessStmtNodes */, 7, 34, 7, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStatementStep = { { 955 /* processStatementStep */, 10, 42, 10, 52, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig readEndStatementExprResult = { { 1010 /* readEndStatementExprResult */, 2, 53, 2, 55, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1037 /* processBodyStmtNodesTwoPass */, 7, 56, 7, 63, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getClosureProcessor = { { 1099 /* getClosureProcessor */, 0, 64, 0, 64, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 1173 /* processDeferredByRefClosureBodies */, 5, 65, 5, 70, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig observeClosureSignatures = { { 1244 /* observeClosureSignatures */, 8, 71, 8, 79, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig withRecordedConstraints = { { 1296 /* withRecordedConstraints */, 3, 80, 3, 83, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig containsLabels = { { 1320 /* containsLabels */, 1, 84, 1, 85, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig appendRecordedStatementResults = { { 1343 /* appendRecordedStatementResults */, 3, 86, 3, 89, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getVariableMentionFlow = { { 1404 /* getVariableMentionFlow */, 1, 90, 1, 91, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 1441 /* statementMentionsAnyVariable */, 2, 92, 2, 94, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectMentionedVariables = { { 1500 /* collectMentionedVariables */, 3, 95, 3, 98, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getOverridingThrowPoints = { { 1536 /* getOverridingThrowPoints */, 2, 99, 2, 101, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmtVarAnnotation = { { 1593 /* processStmtVarAnnotation */, 6, 102, 6, 108, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 1644 /* findSingleVariableLessVarTag */, 2, 109, 2, 111, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig emitVarTagChangedNode = { { 1727 /* emitVarTagChangedNode */, 6, 112, 6, 118, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig __construct = { { 345 /* __construct */, 6, 0, 6, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processNodesWithStorage = { { 532 /* processNodesWithStorage */, 5, 6, 5, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveBackwardGotoScope = { { 694 /* resolveBackwardGotoScope */, 8, 12, 8, 20, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeForwardGotoExitPoints = { { 758 /* mergeForwardGotoExitPoints */, 4, 21, 4, 25, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processUnreachableStatement = { { 795 /* processUnreachableStatement */, 5, 26, 5, 31, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getNextUnreachableStatements = { { 836 /* getNextUnreachableStatements */, 2, 32, 2, 34, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig doProcessStmtNodes = { { 912 /* doProcessStmtNodes */, 7, 35, 7, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStatementStep = { { 1029 /* processStatementStep */, 10, 43, 10, 53, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig readEndStatementExprResult = { { 1084 /* readEndStatementExprResult */, 2, 54, 2, 56, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1111 /* processBodyStmtNodesTwoPass */, 7, 57, 7, 64, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig inferStaticVariableTypes = { { 1291 /* inferStaticVariableTypes */, 10, 65, 10, 75, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1352 /* collectStaticVariableTypes */, 5, 76, 5, 81, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig joinStaticVariableTypes = { { 1406 /* joinStaticVariableTypes */, 5, 82, 5, 87, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClosureProcessor = { { 1464 /* getClosureProcessor */, 0, 88, 0, 88, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 1490 /* processDeferredByRefClosureBodies */, 5, 89, 5, 94, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig observeClosureSignatures = { { 1553 /* observeClosureSignatures */, 8, 95, 8, 103, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withRecordedConstraints = { { 1605 /* withRecordedConstraints */, 3, 104, 3, 107, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig containsLabels = { { 1629 /* containsLabels */, 1, 108, 1, 109, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig appendRecordedStatementResults = { { 1652 /* appendRecordedStatementResults */, 3, 110, 3, 113, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getVariableMentionFlow = { { 1713 /* getVariableMentionFlow */, 1, 114, 1, 115, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 1750 /* statementMentionsAnyVariable */, 2, 116, 2, 118, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectMentionedVariables = { { 1803 /* collectMentionedVariables */, 3, 119, 3, 122, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getOverridingThrowPoints = { { 1839 /* getOverridingThrowPoints */, 2, 123, 2, 125, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmtVarAnnotation = { { 1896 /* processStmtVarAnnotation */, 6, 126, 6, 132, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 1947 /* findSingleVariableLessVarTag */, 2, 133, 2, 135, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2030 /* emitVarTagChangedNode */, 6, 136, 6, 142, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StatementsHandler diff --git a/turbo-ext/src/generated/StaticVariableHandler.h b/turbo-ext/src/generated/StaticVariableHandler.h index 05dd5566eea..b65f68f2caa 100644 --- a/turbo-ext/src/generated/StaticVariableHandler.h +++ b/turbo-ext/src/generated/StaticVariableHandler.h @@ -11,6 +11,7 @@ namespace ptdecl::StaticVariableHandler { /* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ namespace slot { inline constexpr uint32_t varAnnotationProcessor = 0; +inline constexpr uint32_t staticVariableInference = 1; } // namespace slot inline void declareClass(reg::Class &cls) @@ -23,6 +24,7 @@ inline void declareClass(reg::Class &cls) inline void declareProperties(reg::Class &cls) { cls.property("varAnnotationProcessor", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\VarAnnotationProcessor"); + cls.property("staticVariableInference", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\Generics\\StaticVariableInference"); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -30,42 +32,45 @@ namespace sigtab { inline constexpr char strings[] = "varAnnotationProcessor\0" /* 0 */ "PHPStan\\Analyser\\VarAnnotationProcessor\0" /* 23 */ - "__construct\0" /* 63 */ - "stmt\0" /* 75 */ - "PhpParser\\Node\\Stmt\0" /* 80 */ - "\0" /* 100 */ - "supports\0" /* 101 */ - "nodeScopeResolver\0" /* 110 */ - "PHPStan\\Analyser\\NodeScopeResolver\0" /* 128 */ - "scope\0" /* 163 */ - "PHPStan\\Analyser\\MutatingScope\0" /* 169 */ - "storage\0" /* 200 */ - "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 208 */ - "nodeCallback\0" /* 249 */ - "context\0" /* 262 */ - "PHPStan\\Analyser\\StatementContext\0" /* 270 */ - "PHPStan\\Analyser\\InternalStatementResult\0" /* 304 */ - "processStmt"; /* 345 */ + "staticVariableInference\0" /* 63 */ + "PHPStan\\Analyser\\Generics\\StaticVariableInference\0" /* 87 */ + "__construct\0" /* 137 */ + "stmt\0" /* 149 */ + "PhpParser\\Node\\Stmt\0" /* 154 */ + "\0" /* 174 */ + "supports\0" /* 175 */ + "nodeScopeResolver\0" /* 184 */ + "PHPStan\\Analyser\\NodeScopeResolver\0" /* 202 */ + "scope\0" /* 237 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 243 */ + "storage\0" /* 274 */ + "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 282 */ + "nodeCallback\0" /* 323 */ + "context\0" /* 336 */ + "PHPStan\\Analyser\\StatementContext\0" /* 344 */ + "PHPStan\\Analyser\\InternalStatementResult\0" /* 378 */ + "processStmt"; /* 419 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 23), /* __construct $varAnnotationProcessor */ - reg::packed(75, 0, 80), /* supports $stmt */ - reg::packed(100, MAY_BE_BOOL), /* supports return */ - reg::packed(110, 0, 128), /* processStmt $nodeScopeResolver */ - reg::packed(75, 0, 80), /* processStmt $stmt */ - reg::packed(163, 0, 169), /* processStmt $scope */ - reg::packed(200, 0, 208), /* processStmt $storage */ - reg::packed(249, MAY_BE_CALLABLE), /* processStmt $nodeCallback */ - reg::packed(262, 0, 270), /* processStmt $context */ - reg::packed(100, 0, 304), /* processStmt return */ + reg::packed(63, 0, 87), /* __construct $staticVariableInference */ + reg::packed(149, 0, 154), /* supports $stmt */ + reg::packed(174, MAY_BE_BOOL), /* supports return */ + reg::packed(184, 0, 202), /* processStmt $nodeScopeResolver */ + reg::packed(149, 0, 154), /* processStmt $stmt */ + reg::packed(237, 0, 243), /* processStmt $scope */ + reg::packed(274, 0, 282), /* processStmt $storage */ + reg::packed(323, MAY_BE_CALLABLE), /* processStmt $nodeCallback */ + reg::packed(336, 0, 344), /* processStmt $context */ + reg::packed(174, 0, 378), /* processStmt 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 = { { 63 /* __construct */, 1, 0, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig supports = { { 101 /* supports */, 1, 1, 1, 2, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmt = { { 345 /* processStmt */, 6, 3, 6, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig __construct = { { 137 /* __construct */, 2, 0, 2, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig supports = { { 175 /* supports */, 1, 2, 1, 3, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmt = { { 419 /* processStmt */, 6, 4, 6, 10, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StaticVariableHandler diff --git a/turbo-ext/src/generated/StaticVariableInference.h b/turbo-ext/src/generated/StaticVariableInference.h new file mode 100644 index 00000000000..f371b9346f0 --- /dev/null +++ b/turbo-ext/src/generated/StaticVariableInference.h @@ -0,0 +1,86 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Analyser/Generics/StaticVariableInference.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_STATIC_VARIABLE_INFERENCE_H +#define PHPSTANTURBO_GENERATED_STATIC_VARIABLE_INFERENCE_H + +#include "../reg.h" + +namespace ptdecl::StaticVariableInference { + +/* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ +namespace slot { +inline constexpr uint32_t enabled = 0; +} // 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("enabled", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "enabled\0" /* 0 */ + "__construct\0" /* 8 */ + "functionLike\0" /* 20 */ + "PhpParser\\Node\0" /* 33 */ + "stmts\0" /* 48 */ + "\0" /* 54 */ + "getSites\0" /* 55 */ + "scope\0" /* 64 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 70 */ + "var\0" /* 101 */ + "PhpParser\\Node\\Expr\\Variable\0" /* 105 */ + "isInferred\0" /* 134 */ + "getResolvedTypes\0" /* 145 */ + "scanSites\0" /* 162 */ + "expr\0" /* 172 */ + "PhpParser\\Node\\Expr\0" /* 177 */ + "names\0" /* 197 */ + "collectRootNames\0" /* 203 */ + "stmt\0" /* 220 */ + "PhpParser\\Node\\Stmt\\Static_\0" /* 225 */ + "hasVarTag"; /* 253 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, MAY_BE_BOOL), /* __construct $enabled */ + reg::packed(20, 0, 33), /* getSites $functionLike */ + reg::packed(48, MAY_BE_ARRAY), /* getSites $stmts */ + reg::packed(54, MAY_BE_ARRAY), /* getSites return */ + reg::packed(64, 0, 70), /* isInferred $scope */ + reg::packed(101, 0, 105), /* isInferred $var */ + reg::packed(54, MAY_BE_BOOL), /* isInferred return */ + reg::packed(64, 0, 70), /* getResolvedTypes $scope */ + reg::packed(101, 0, 105), /* getResolvedTypes $var */ + reg::packed(54, MAY_BE_NULL | MAY_BE_ARRAY), /* getResolvedTypes return */ + reg::packed(48, MAY_BE_ARRAY), /* scanSites $stmts */ + reg::packed(54, MAY_BE_ARRAY), /* scanSites return */ + reg::packed(172, 0, 177), /* collectRootNames $expr */ + reg::packed(197, MAY_BE_ARRAY, reg::NoString, true, false), /* collectRootNames $names */ + reg::packed(54, MAY_BE_VOID), /* collectRootNames return */ + reg::packed(220, 0, 225), /* hasVarTag $stmt */ + reg::packed(54, MAY_BE_BOOL), /* hasVarTag 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 = { { 8 /* __construct */, 1, 0, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getSites = { { 55 /* getSites */, 2, 1, 2, 3, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInferred = { { 134 /* isInferred */, 2, 4, 2, 6, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getResolvedTypes = { { 145 /* getResolvedTypes */, 2, 7, 2, 9, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig scanSites = { { 162 /* scanSites */, 1, 10, 1, 11, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig collectRootNames = { { 203 /* collectRootNames */, 2, 12, 2, 14, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig hasVarTag = { { 253 /* hasVarTag */, 1, 15, 1, 16, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +} // namespace sig + +} // namespace ptdecl::StaticVariableInference + +#endif diff --git a/turbo-ext/src/generated/TemplateArgumentFrame.h b/turbo-ext/src/generated/TemplateArgumentFrame.h index d4244e10dcb..6f39bfe7a3d 100644 --- a/turbo-ext/src/generated/TemplateArgumentFrame.h +++ b/turbo-ext/src/generated/TemplateArgumentFrame.h @@ -18,6 +18,7 @@ inline constexpr uint32_t closureSignatureStmts = 4; inline constexpr uint32_t settledClosureSites = 5; inline constexpr uint32_t observingClosures = 6; inline constexpr uint32_t byRefSites = 7; +inline constexpr uint32_t staticVariableTypes = 8; } // namespace slot inline void declareClass(reg::Class &cls) @@ -36,6 +37,7 @@ inline void declareProperties(reg::Class &cls) cls.property("settledClosureSites", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); cls.property("observingClosures", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_BOOL); cls.property("byRefSites", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); + cls.property("staticVariableTypes", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -64,30 +66,36 @@ inline constexpr char strings[] = "observingClosures\0" /* 335 */ "false\0" /* 353 */ "byRefSites\0" /* 359 */ - "__construct\0" /* 370 */ - "getByRefSiteMode\0" /* 382 */ - "getLocalByRefSites\0" /* 399 */ - "closureResolutions\0" /* 418 */ - "closureSiteStatementIndexes\0" /* 437 */ - "withObservedClosures\0" /* 465 */ - "isSettledClosureSite\0" /* 486 */ - "getParent\0" /* 507 */ - "getClosureSignatureBody\0" /* 517 */ - "getClosureSignatureStmts\0" /* 541 */ - "isObserving\0" /* 566 */ - "isObservingClosures\0" /* 578 */ - "firstSiteStatementIndex\0" /* 598 */ - "statementIndex\0" /* 622 */ - "ownsSiteInStatement\0" /* 637 */ - "hasSiteAtOrAfter\0" /* 657 */ - "template\0" /* 674 */ - "PHPStan\\Type\\Generic\\TemplateType\0" /* 683 */ - "resolveOrUnconstrained\0" /* 717 */ - "resolve\0" /* 740 */ - "resolveUnconstrained\0" /* 748 */ - "templateName\0" /* 769 */ - "getResolutionCacheKeySuffix\0" /* 782 */ - "key"; /* 810 */ + "staticVariableTypes\0" /* 370 */ + "__construct\0" /* 390 */ + "var\0" /* 402 */ + "PhpParser\\Node\\Expr\\Variable\0" /* 406 */ + "getStaticVariableTypes\0" /* 435 */ + "statementIndexes\0" /* 458 */ + "withStaticVariableTypes\0" /* 475 */ + "getByRefSiteMode\0" /* 499 */ + "getLocalByRefSites\0" /* 516 */ + "closureResolutions\0" /* 535 */ + "closureSiteStatementIndexes\0" /* 554 */ + "withObservedClosures\0" /* 582 */ + "isSettledClosureSite\0" /* 603 */ + "getParent\0" /* 624 */ + "getClosureSignatureBody\0" /* 634 */ + "getClosureSignatureStmts\0" /* 658 */ + "isObserving\0" /* 683 */ + "isObservingClosures\0" /* 695 */ + "firstSiteStatementIndex\0" /* 715 */ + "statementIndex\0" /* 739 */ + "ownsSiteInStatement\0" /* 754 */ + "hasSiteAtOrAfter\0" /* 774 */ + "template\0" /* 791 */ + "PHPStan\\Type\\Generic\\TemplateType\0" /* 800 */ + "resolveOrUnconstrained\0" /* 834 */ + "resolve\0" /* 857 */ + "resolveUnconstrained\0" /* 865 */ + "templateName\0" /* 886 */ + "getResolutionCacheKeySuffix\0" /* 899 */ + "key"; /* 927 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* returnTypeOfCall $acceptor */ reg::packed(47, 0, 53), /* returnTypeOfCall $scope */ @@ -102,11 +110,17 @@ inline constexpr reg::PackedArg args[] = { reg::packed(315, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $settledClosureSites */ reg::packed(335, MAY_BE_BOOL, reg::NoString, false, false, 353), /* __construct $observingClosures */ reg::packed(359, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $byRefSites */ + reg::packed(370, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $staticVariableTypes */ + reg::packed(402, 0, 406), /* getStaticVariableTypes $var */ + reg::packed(130, MAY_BE_NULL | MAY_BE_ARRAY), /* getStaticVariableTypes return */ + reg::packed(370, MAY_BE_ARRAY), /* withStaticVariableTypes $staticVariableTypes */ + reg::packed(458, MAY_BE_ARRAY), /* withStaticVariableTypes $statementIndexes */ + reg::packed(130, 0, 173), /* withStaticVariableTypes return */ reg::packed(84, 0, 89), /* getByRefSiteMode $site */ reg::packed(130, MAY_BE_NULL | MAY_BE_STRING), /* getByRefSiteMode return */ reg::packed(130, MAY_BE_ARRAY), /* getLocalByRefSites return */ - reg::packed(418, MAY_BE_ARRAY), /* withObservedClosures $closureResolutions */ - reg::packed(437, MAY_BE_ARRAY), /* withObservedClosures $closureSiteStatementIndexes */ + reg::packed(535, MAY_BE_ARRAY), /* withObservedClosures $closureResolutions */ + reg::packed(554, MAY_BE_ARRAY), /* withObservedClosures $closureSiteStatementIndexes */ reg::packed(315, MAY_BE_ARRAY), /* withObservedClosures $settledClosureSites */ reg::packed(359, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* withObservedClosures $byRefSites */ reg::packed(130, 0, 173), /* withObservedClosures return */ @@ -118,23 +132,23 @@ inline constexpr reg::PackedArg args[] = { reg::packed(130, MAY_BE_BOOL), /* isObserving return */ reg::packed(130, MAY_BE_BOOL), /* isObservingClosures return */ reg::packed(130, MAY_BE_NULL | MAY_BE_LONG), /* firstSiteStatementIndex return */ - reg::packed(622, MAY_BE_LONG), /* ownsSiteInStatement $statementIndex */ + reg::packed(739, MAY_BE_LONG), /* ownsSiteInStatement $statementIndex */ reg::packed(130, MAY_BE_BOOL), /* ownsSiteInStatement return */ - reg::packed(622, MAY_BE_LONG), /* hasSiteAtOrAfter $statementIndex */ + reg::packed(739, MAY_BE_LONG), /* hasSiteAtOrAfter $statementIndex */ reg::packed(130, MAY_BE_BOOL), /* hasSiteAtOrAfter return */ reg::packed(84, 0, 89), /* resolveOrUnconstrained $site */ - reg::packed(674, 0, 683), /* resolveOrUnconstrained $template */ + reg::packed(791, 0, 800), /* resolveOrUnconstrained $template */ reg::packed(130, 0, 131), /* resolveOrUnconstrained return */ reg::packed(84, 0, 89), /* resolveUnconstrained $site */ - reg::packed(674, 0, 683), /* resolveUnconstrained $template */ - reg::packed(740, MAY_BE_CALLABLE), /* resolveUnconstrained $resolve */ + reg::packed(791, 0, 800), /* resolveUnconstrained $template */ + reg::packed(857, MAY_BE_CALLABLE), /* resolveUnconstrained $resolve */ reg::packed(130, 0, 131), /* resolveUnconstrained return */ reg::packed(84, 0, 89), /* resolve $site */ - reg::packed(769, MAY_BE_STRING), /* resolve $templateName */ + reg::packed(886, MAY_BE_STRING), /* resolve $templateName */ reg::packed(130, MAY_BE_NULL, 131), /* resolve return */ reg::packed(130, MAY_BE_STRING), /* getResolutionCacheKeySuffix return */ reg::packed(84, 0, 89), /* key $site */ - reg::packed(769, MAY_BE_STRING), /* key $templateName */ + reg::packed(886, MAY_BE_STRING), /* key $templateName */ reg::packed(130, MAY_BE_STRING), /* key return */ }; using Sig = reg::Sig; @@ -143,24 +157,26 @@ using Sig = reg::Sig; /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { inline constexpr sigtab::Sig returnTypeOfCall = { { 149 /* returnTypeOfCall */, 3, 0, 4, 4, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig __construct = { { 370 /* __construct */, 1, 5, 8, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getByRefSiteMode = { { 382 /* getByRefSiteMode */, 1, 13, 1, 14, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getLocalByRefSites = { { 399 /* getLocalByRefSites */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig withObservedClosures = { { 465 /* withObservedClosures */, 3, 16, 4, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSettledClosureSite = { { 486 /* isSettledClosureSite */, 1, 21, 1, 22, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getParent = { { 507 /* getParent */, 0, 23, 0, 23, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getClosureSignatureBody = { { 517 /* getClosureSignatureBody */, 0, 24, 0, 24, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getClosureSignatureStmts = { { 541 /* getClosureSignatureStmts */, 0, 25, 0, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isObserving = { { 566 /* isObserving */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isObservingClosures = { { 578 /* isObservingClosures */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig firstSiteStatementIndex = { { 598 /* firstSiteStatementIndex */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig ownsSiteInStatement = { { 637 /* ownsSiteInStatement */, 1, 29, 1, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasSiteAtOrAfter = { { 657 /* hasSiteAtOrAfter */, 1, 31, 1, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveOrUnconstrained = { { 717 /* resolveOrUnconstrained */, 2, 33, 2, 35, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveUnconstrained = { { 748 /* resolveUnconstrained */, 3, 36, 3, 39, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolve = { { 740 /* resolve */, 2, 40, 2, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getResolutionCacheKeySuffix = { { 782 /* getResolutionCacheKeySuffix */, 0, 43, 0, 43, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig key = { { 810 /* key */, 2, 44, 2, 46, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig __construct = { { 390 /* __construct */, 1, 5, 9, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getStaticVariableTypes = { { 435 /* getStaticVariableTypes */, 1, 14, 1, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withStaticVariableTypes = { { 475 /* withStaticVariableTypes */, 2, 16, 2, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getByRefSiteMode = { { 499 /* getByRefSiteMode */, 1, 19, 1, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getLocalByRefSites = { { 516 /* getLocalByRefSites */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withObservedClosures = { { 582 /* withObservedClosures */, 3, 22, 4, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSettledClosureSite = { { 603 /* isSettledClosureSite */, 1, 27, 1, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getParent = { { 624 /* getParent */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClosureSignatureBody = { { 634 /* getClosureSignatureBody */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClosureSignatureStmts = { { 658 /* getClosureSignatureStmts */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isObserving = { { 683 /* isObserving */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isObservingClosures = { { 695 /* isObservingClosures */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig firstSiteStatementIndex = { { 715 /* firstSiteStatementIndex */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig ownsSiteInStatement = { { 754 /* ownsSiteInStatement */, 1, 35, 1, 36, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasSiteAtOrAfter = { { 774 /* hasSiteAtOrAfter */, 1, 37, 1, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveOrUnconstrained = { { 834 /* resolveOrUnconstrained */, 2, 39, 2, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveUnconstrained = { { 865 /* resolveUnconstrained */, 3, 42, 3, 45, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolve = { { 857 /* resolve */, 2, 46, 2, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getResolutionCacheKeySuffix = { { 899 /* getResolutionCacheKeySuffix */, 0, 49, 0, 49, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig key = { { 927 /* key */, 2, 50, 2, 52, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::TemplateArgumentFrame diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index d55ebcff85b..2409b35eb4a 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -2010,6 +2010,16 @@ zv::Val pt_template_argument_frame_with_observed_closures(zval *frame, zval *clo zv::Val pt_template_argument_frame_get_by_ref_site_mode(zval *frame, zval *site); /* TemplateArgumentFrame::getLocalByRefSites(); UNDEF = pending exception */ zv::Val pt_template_argument_frame_get_local_by_ref_sites(zval *frame); +/* TemplateArgumentFrame::getStaticVariableTypes() ([phpdoc, native] or PHP + * null) / withStaticVariableTypes(); UNDEF = pending exception */ +zv::Val pt_template_argument_frame_get_static_variable_types(zval *frame, zval *var); +zv::Val pt_template_argument_frame_with_static_variable_types(zval *frame, zval *staticVariableTypes, zval *statementIndexes); +/* StaticVariableInference.cpp: getSites() / isInferred() (false = pending + * exception) / getResolvedTypes() on the service; UNDEF = pending exception */ +extern zend_class_entry *pt_ce_static_variable_inference; +zv::Val pt_static_variable_inference_get_sites(zval *inference, zval *functionLike, zval *stmts); +[[nodiscard]] bool pt_static_variable_inference_is_inferred(zval *inference, zval *scope, zval *var, bool &out); +zv::Val pt_static_variable_inference_get_resolved_types(zval *inference, zval *scope, zval *var); /* $frame->isSettledClosureSite($site); false = pending exception */ [[nodiscard]] bool pt_template_argument_frame_is_settled_closure_site(zval *frame, zval *site, bool &out); /* $frame->getClosureSignatureBody() / getClosureSignatureStmts(); UNDEF = diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index 9b782f87d3a..cf60242c5f3 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -4615,6 +4615,7 @@ public function getComments(): array \PHPStan\Analyser\ParametersProcessor::class => 'walk-trace.php', \PHPStan\Analyser\ExprHandler\Helper\ContextualClosureParameterResolver::class => 'walk-trace.php', \PHPStan\Analyser\Generics\ClosureSignatureInference::class => 'walk-trace.php', + \PHPStan\Analyser\Generics\StaticVariableInference::class => 'walk-trace.php', \PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver::class => 'walk-trace.php', \PHPStan\Analyser\ExprHandler\Helper\ClosureParameterResolver::class => 'walk-trace.php', \PHPStan\Analyser\ClosureProcessor::class => 'walk-trace.php', From a49a4e69fd96a514c0a14c294c2bafb15b0c8f6c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 27 Sep 2026 06:16:11 +0200 Subject: [PATCH 2/3] Bump expected turbo version --- src/Turbo/TurboExtensionEnabler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 22d633e9bf4..9f266221fd9 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = 'a5324bf'; + public const EXPECTED_EXTENSION_VERSION = 'e430d0e'; private static bool $active = false; From 8dd80bc4d20c52e878b1530001d7b415ed5f95c4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 27 Sep 2026 14:27:25 +0200 Subject: [PATCH 3/3] Add regression test for #7066 Closes https://github.com/phpstan/phpstan/issues/7066 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01VpbB99tJfUmeArX74xqvHv --- tests/PHPStan/Analyser/nsrt/bug-7066.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-7066.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-7066.php b/tests/PHPStan/Analyser/nsrt/bug-7066.php new file mode 100644 index 00000000000..03a51e75df9 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7066.php @@ -0,0 +1,19 @@ +