diff --git a/conf/config.neon b/conf/config.neon index ff60f350616..4999a0cc6e7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -104,6 +104,7 @@ parameters: reportStaticMethodSignatures: false reportWrongPhpDocTypeInVarTag: false reportAnyTypeWideningInVarTag: false + reportRedundantVarTag: false reportNonIntStringArrayKey: false reportUnsafeArrayStringKeyCasting: null reportPossiblyNonexistentGeneralArrayOffset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 62dab9d9c3b..348e8e81e05 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -108,6 +108,7 @@ parametersSchema: reportStaticMethodSignatures: bool() reportWrongPhpDocTypeInVarTag: bool() reportAnyTypeWideningInVarTag: bool() + reportRedundantVarTag: bool() reportNonIntStringArrayKey: bool() reportUnsafeArrayStringKeyCasting: schema(string(), pattern('detect|prevent'), nullable()) reportPossiblyNonexistentGeneralArrayOffset: bool() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b85f94a6b81..97ae291ff65 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -451,6 +451,7 @@ public function processStmtNode( if ($stmtHandler !== null) { $stmtResult = $stmtHandler->processStmt($this, $stmt, $scope, $storage, $nodeCallback, $context); if ($varTagDependencies !== null) { + $this->statementsHandler->emitRedundantVarTagNodes($this, $scope, $storage, $stmt, $nodeCallback); $stmtResult = $stmtResult->withDependencies(Dependencies::merge($stmtResult->getDependencies(), $varTagDependencies)); } if ($overridingThrowPoints !== null) { diff --git a/src/Analyser/RedundantVarTagDetector.php b/src/Analyser/RedundantVarTagDetector.php new file mode 100644 index 00000000000..dfc81a15ec9 --- /dev/null +++ b/src/Analyser/RedundantVarTagDetector.php @@ -0,0 +1,183 @@ +}> statement id => [statement, tag key => [tag, changed nothing so far]] */ + private array $applications = []; + + public function resetFileAnalysisState(): void + { + $this->applications = []; + } + + /** + * Records the application of $varTag to a variable, which gets the tag's + * type, and becomes certainly defined when $defines is true. $narrowedByForeach + * is true for the tag of a foreach's iteratee applied again on entering the + * loop body, where the loop may have narrowed the variable to a non-empty + * array the tag widens again. + * + * @param int|string $key the tag's key in ResolvedPhpDocBlock::getVarTags() + */ + public function recordVariable(Node\Stmt $stmt, $key, VarTag $varTag, MutatingScope $scope, string $variableName, bool $defines, bool $narrowedByForeach = false): void + { + $certainty = $scope->hasVariableType($variableName); + if ($certainty->no() || ($defines && !$certainty->yes())) { + $this->record($stmt, $key, $varTag, false); + return; + } + + $originalType = $scope->getVariableType($variableName); + $tagType = $varTag->getType(); + $this->record( + $stmt, + $key, + $varTag, + $this->isSameType($originalType, $tagType) + || ($narrowedByForeach && $this->isSameType($originalType, TypeCombinator::remove($tagType, new ConstantArrayType([], [])))), + ); + } + + /** + * Records the application of the variable-less $varTag to $expr, $scope + * being the scope before the tag re-typed it. + */ + public function recordExpression(NodeScopeResolver $nodeScopeResolver, Node\Stmt $stmt, VarTag $varTag, Expr $expr, MutatingScope $scope): void + { + // priced again from scratch: the walk's results of the expression and + // of its parts saw the tag's type, re-typing $a['k'] narrows $a as well + $originalType = $nodeScopeResolver->processExprOnDemand($expr, $scope, new ExpressionResultStorage())->getType(); + $this->record($stmt, 0, $varTag, $this->isSameType($originalType, $varTag->getType())); + } + + /** + * Emits a RedundantVarTagNode for each tag above $stmt that changed + * nothing in any of its applications so far. + * + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + public function emitNodes(NodeScopeResolver $nodeScopeResolver, MutatingScope $scope, ExpressionResultStorage $storage, Node\Stmt $stmt, callable $nodeCallback): void + { + $id = spl_object_id($stmt); + if (!isset($this->applications[$id]) || $this->applications[$id][0] !== $stmt) { + return; + } + + foreach ($this->applications[$id][1] as $key => [$varTag, $changedNothing]) { + if (!$changedNothing) { + continue; + } + + $nodeScopeResolver->callNodeCallback($nodeCallback, new RedundantVarTagNode($stmt, $varTag, is_string($key) ? $key : null), $scope, $storage); + } + } + + /** + * @param int|string $key + */ + private function record(Node\Stmt $stmt, $key, VarTag $varTag, bool $changedNothing): void + { + $id = spl_object_id($stmt); + if (!isset($this->applications[$id]) || $this->applications[$id][0] !== $stmt) { + // the id of a statement freed since is reused + $this->applications[$id] = [$stmt, []]; + } + + $changedNothingBefore = $this->applications[$id][1][$key][1] ?? true; + $this->applications[$id][1][$key] = [$varTag, $changedNothingBefore && $changedNothing]; + } + + private function isSameType(Type $originalType, Type $tagType): bool + { + if ( + // equals() is one-way for a type extending another one, like a + // template type or a benevolent union, and does not compare + // whether a constant array is a list + !$originalType->equals($tagType) + || !$tagType->equals($originalType) + || !$originalType->isList()->equals($tagType->isList()) + ) { + return false; + } + + // equals() also ignores whether mixed and never are explicit, which + // decides what is reported about them + $kinds = $this->getMixedAndNeverKinds($originalType); + + return $kinds !== null && $kinds === $this->getMixedAndNeverKinds($tagType); + } + + /** + * Which kinds of mixed and never the type contains; null when it contains an + * error type, or both kinds of mixed or of never, whose positions are not compared. + */ + private function getMixedAndNeverKinds(Type $type): ?int + { + $kinds = 0; + $containsError = false; + TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$kinds, &$containsError): Type { + if ($type instanceof ErrorType) { + $containsError = true; + return $type; + } + + if ($type instanceof MixedType) { + $kinds |= $type->isExplicitMixed() ? self::EXPLICIT_MIXED : self::IMPLICIT_MIXED; + } elseif ($type instanceof NeverType) { + $kinds |= $type->isExplicit() ? self::EXPLICIT_NEVER : self::IMPLICIT_NEVER; + } + + return $traverse($type); + }); + + if ( + $containsError + || ($kinds & (self::EXPLICIT_MIXED | self::IMPLICIT_MIXED)) === (self::EXPLICIT_MIXED | self::IMPLICIT_MIXED) + || ($kinds & (self::EXPLICIT_NEVER | self::IMPLICIT_NEVER)) === (self::EXPLICIT_NEVER | self::IMPLICIT_NEVER) + ) { + return null; + } + + return $kinds; + } + +} diff --git a/src/Analyser/StatementsHandler.php b/src/Analyser/StatementsHandler.php index e8db16423f1..f94e3e62fff 100644 --- a/src/Analyser/StatementsHandler.php +++ b/src/Analyser/StatementsHandler.php @@ -89,8 +89,11 @@ public function __construct( private TemplateArgumentResolver $templateArgumentResolver, private Container $container, private StaticVariableInference $staticVariableInference, + private RedundantVarTagDetector $redundantVarTagDetector, #[AutowiredParameter(ref: '%featureToggles.unresolvedTemplateArguments%')] private bool $unresolvedTemplateArguments, + #[AutowiredParameter(ref: '%reportRedundantVarTag%')] + private bool $reportRedundantVarTag, ) { $this->debugTemplateArguments = getenv('PHPSTAN_TEMPLATE_ARGUMENTS_DEBUG') === '1'; @@ -1977,6 +1980,10 @@ public function processStmtVarAnnotation(NodeScopeResolver $nodeScopeResolver, M $scope = $scope->addTemplateArgumentConstraints($this->templateArgumentObserver->collectSend($varTag->getType(), $originalType)); } + if ($this->reportRedundantVarTag) { + $this->redundantVarTagDetector->recordVariable($stmt, $name, $varTag, $scope, $name, $certainty->yes()); + } + $nativeScope = $scope->doNotTreatPhpDocTypesAsCertain(); $scope = $scope->assignVariable( $name, @@ -2047,6 +2054,9 @@ public function emitVarTagChangedNode(NodeScopeResolver $nodeScopeResolver, Muta } $nodeScopeResolver->callNodeCallback($nodeCallback, new VarTagChangedExpressionTypeNode($varTag, $defaultExpr), $scope, $storage); + if ($this->reportRedundantVarTag) { + $this->redundantVarTagDetector->recordExpression($nodeScopeResolver, $stmt, $varTag, $defaultExpr, $scope); + } $defaultExprResult = $storage->findExpressionResult($defaultExpr); if ($defaultExprResult === null || $nodeScopeResolver->observingTemplateArgumentFrame($defaultExprResult->getScope()) === null) { return TemplateArgumentConstraints::createEmpty(); @@ -2054,4 +2064,20 @@ public function emitVarTagChangedNode(NodeScopeResolver $nodeScopeResolver, Muta return $this->templateArgumentObserver->collectSend($varTag->getType(), $defaultExprResult->getType()); } + /** + * Emits the nodes the redundant @var tag rule listens to, for the tags above + * $stmt that changed nothing in any application so far - called once the + * statement has been processed (see RedundantVarTagDetector). + * + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + public function emitRedundantVarTagNodes(NodeScopeResolver $nodeScopeResolver, MutatingScope $scope, ExpressionResultStorage $storage, Node\Stmt $stmt, callable $nodeCallback): void + { + if (!$this->reportRedundantVarTag) { + return; + } + + $this->redundantVarTagDetector->emitNodes($nodeScopeResolver, $scope, $storage, $stmt, $nodeCallback); + } + } diff --git a/src/Analyser/StmtHandler/ForeachHandler.php b/src/Analyser/StmtHandler/ForeachHandler.php index 2ba5323bf7b..ef12a08e069 100644 --- a/src/Analyser/StmtHandler/ForeachHandler.php +++ b/src/Analyser/StmtHandler/ForeachHandler.php @@ -537,7 +537,7 @@ static function () use ($condResult, $emptyArrayType): Type { private function enterForeach(NodeScopeResolver $nodeScopeResolver, MutatingScope $scope, ExpressionResultStorage $storage, MutatingScope $originalScope, Foreach_ $stmt, Type $iterateeType, Type $nativeIterateeType, callable $nodeCallback): MutatingScope { if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { - $scope = $this->varAnnotationProcessor->processVarAnnotation($scope, [$stmt->expr->name], $stmt); + $scope = $this->varAnnotationProcessor->processForeachIterateeVarAnnotation($scope, $stmt->expr->name, $stmt); } if ( diff --git a/src/Analyser/VarAnnotationProcessor.php b/src/Analyser/VarAnnotationProcessor.php index 56c985e27ec..f1271e274b4 100644 --- a/src/Analyser/VarAnnotationProcessor.php +++ b/src/Analyser/VarAnnotationProcessor.php @@ -4,6 +4,7 @@ use PhpParser\Comment\Doc; use PhpParser\Node; +use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\TrinaryLogic; use PHPStan\Turbo\ShadowedByTurboExtension; @@ -18,6 +19,9 @@ final class VarAnnotationProcessor public function __construct( private FileTypeMapper $fileTypeMapper, + private RedundantVarTagDetector $redundantVarTagDetector, + #[AutowiredParameter(ref: '%reportRedundantVarTag%')] + private bool $reportRedundantVarTag, ) { } @@ -26,6 +30,25 @@ public function __construct( * @param array $variableNames */ public function processVarAnnotation(MutatingScope $scope, array $variableNames, Node\Stmt $node, bool &$changed = false): MutatingScope + { + return $this->applyVarTags($scope, $variableNames, $node, $changed, false); + } + + /** + * processVarAnnotation() for the variable a foreach iterates, applied again on + * entering the loop body, where the loop may have narrowed it to a non-empty array. + */ + public function processForeachIterateeVarAnnotation(MutatingScope $scope, string $variableName, Node\Stmt\Foreach_ $node): MutatingScope + { + $changed = false; + + return $this->applyVarTags($scope, [$variableName], $node, $changed, true); + } + + /** + * @param array $variableNames + */ + private function applyVarTags(MutatingScope $scope, array $variableNames, Node\Stmt $node, bool &$changed, bool $narrowedByForeach): MutatingScope { $function = $scope->getFunction(); $varTags = []; @@ -57,12 +80,18 @@ public function processVarAnnotation(MutatingScope $scope, array $variableNames, $variableType = $varTags[$variableName]->getType(); $changed = true; + if ($this->reportRedundantVarTag) { + $this->redundantVarTagDetector->recordVariable($node, $variableName, $varTags[$variableName], $scope, $variableName, true, $narrowedByForeach); + } $scope = $scope->assignVariable($variableName, $variableType, new MixedType(), TrinaryLogic::createYes()); } if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { $variableType = $varTags[0]->getType(); $changed = true; + if ($this->reportRedundantVarTag) { + $this->redundantVarTagDetector->recordVariable($node, 0, $varTags[0], $scope, $variableNames[0], true, $narrowedByForeach); + } $scope = $scope->assignVariable($variableNames[0], $variableType, new MixedType(), TrinaryLogic::createYes()); } diff --git a/src/Node/RedundantVarTagNode.php b/src/Node/RedundantVarTagNode.php new file mode 100644 index 00000000000..f596d0d2c4f --- /dev/null +++ b/src/Node/RedundantVarTagNode.php @@ -0,0 +1,53 @@ +getAttributes()); + } + + public function getStmt(): Stmt + { + return $this->stmt; + } + + public function getVarTag(): VarTag + { + return $this->varTag; + } + + /** Null for a tag without a variable name. */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + #[Override] + public function getType(): string + { + return 'PHPStan_Node_RedundantVarTag'; + } + + /** + * @return string[] + */ + #[Override] + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 004444161d0..48c27ce44c2 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -55,6 +55,9 @@ final class PhpDocNodeResolver { + /** The names of the @var tag, the last one taking precedence. */ + public const VAR_TAG_NAMES = ['@var', '@phan-var', '@psalm-var', '@phpstan-var']; + public function __construct( private TypeNodeResolver $typeNodeResolver, private ConstExprNodeResolver $constExprNodeResolver, @@ -70,7 +73,7 @@ public function resolveVarTags(PhpDocNode $phpDocNode, NameScope $nameScope): ar { $resolved = []; $resolvedByTag = []; - foreach (['@var', '@phan-var', '@psalm-var', '@phpstan-var'] as $tagName) { + foreach (self::VAR_TAG_NAMES as $tagName) { $tagResolved = []; foreach ($phpDocNode->getVarTagValues($tagName) as $tagValue) { $type = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); diff --git a/src/Rules/PhpDoc/RedundantVarTagRule.php b/src/Rules/PhpDoc/RedundantVarTagRule.php new file mode 100644 index 00000000000..a9cdc4cec43 --- /dev/null +++ b/src/Rules/PhpDoc/RedundantVarTagRule.php @@ -0,0 +1,108 @@ + + */ +#[RegisteredRule(level: 2)] +final class RedundantVarTagRule implements Rule +{ + + public function __construct( + private FileTypeMapper $fileTypeMapper, + ) + { + } + + public function getNodeType(): string + { + return RedundantVarTagNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + // a trait is analysed once per class using it, and the tag may be + // redundant in only some of them + if ($scope->isInTrait()) { + return []; + } + + // removing the tag must not let another one take effect: a tag for the + // same variable it overrides, or named tags a variable-less one hides + $tagCounts = $this->countVarTags($node->getStmt(), $scope); + $variableName = $node->getVariableName(); + $type = $node->getVarTag()->getType(); + if ($variableName !== null) { + if (($tagCounts['$' . $variableName] ?? 0) !== 1 || isset($tagCounts[''])) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var for variable $%s with type %s is redundant, the variable already has this type.', + $variableName, + $type->describe(VerbosityLevel::getRecommendedLevelByType($type)), + ))->identifier('varTag.redundant')->build(), + ]; + } + + if ($tagCounts !== ['' => 1]) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var with type %s is redundant, the expression already has this type.', + $type->describe(VerbosityLevel::getRecommendedLevelByType($type)), + ))->identifier('varTag.redundant')->build(), + ]; + } + + /** + * How many @var tags in the PHPDocs above the statement name each variable, + * by its name with the $, or '' for the tags without one. + * + * @return array + */ + private function countVarTags(Node\Stmt $stmt, Scope $scope): array + { + $function = $scope->getFunction(); + $counts = []; + foreach ($stmt->getComments() as $comment) { + if (!$comment instanceof Doc) { + continue; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $comment->getText(), + ); + foreach ($resolvedPhpDoc->getPhpDocNodes() as $phpDocNode) { + foreach (PhpDocNodeResolver::VAR_TAG_NAMES as $tagName) { + foreach ($phpDocNode->getVarTagValues($tagName) as $tagValue) { + $counts[$tagValue->variableName] = ($counts[$tagValue->variableName] ?? 0) + 1; + } + } + } + } + + return $counts; + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/RedundantVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RedundantVarTagRuleTest.php new file mode 100644 index 00000000000..363675d4af5 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/RedundantVarTagRuleTest.php @@ -0,0 +1,128 @@ + + */ +class RedundantVarTagRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new RedundantVarTagRule(self::getContainer()->getByType(FileTypeMapper::class)); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/redundant-var-tag.php'], [ + [ + 'PHPDoc tag @var for variable $a with type string is redundant, the variable already has this type.', + 39, + ], + [ + 'PHPDoc tag @var with type string is redundant, the expression already has this type.', + 42, + ], + [ + 'PHPDoc tag @var for variable $c with type string is redundant, the variable already has this type.', + 45, + ], + [ + 'PHPDoc tag @var for variable $g with type list is redundant, the variable already has this type.', + 57, + ], + [ + 'PHPDoc tag @var for variable $h with type array is redundant, the variable already has this type.', + 60, + ], + [ + 'PHPDoc tag @var for variable $i with type mixed is redundant, the variable already has this type.', + 63, + ], + [ + 'PHPDoc tag @var for variable $a with type string is redundant, the variable already has this type.', + 69, + ], + [ + 'PHPDoc tag @var for variable $k with type int is redundant, the variable already has this type.', + 72, + ], + [ + 'PHPDoc tag @var for variable $m with type string is redundant, the variable already has this type.', + 75, + ], + [ + 'PHPDoc tag @var for variable $value with type int is redundant, the variable already has this type.', + 83, + ], + [ + 'PHPDoc tag @var for variable $key with type int<0, max> is redundant, the variable already has this type.', + 87, + ], + [ + 'PHPDoc tag @var for variable $list with type list is redundant, the variable already has this type.', + 91, + ], + [ + 'PHPDoc tag @var for variable $s with type string is redundant, the variable already has this type.', + 121, + ], + [ + 'PHPDoc tag @var with type string is redundant, the expression already has this type.', + 130, + ], + [ + 'PHPDoc tag @var with type Exception is redundant, the expression already has this type.', + 142, + ], + [ + 'PHPDoc tag @var with type string is redundant, the expression already has this type.', + 225, + ], + [ + 'PHPDoc tag @var with type non-empty-string is redundant, the expression already has this type.', + 233, + ], + [ + 'PHPDoc tag @var for variable $x with type list is redundant, the variable already has this type.', + 391, + ], + [ + 'PHPDoc tag @var for variable $k with type int is redundant, the variable already has this type.', + 419, + ], + [ + 'PHPDoc tag @var for variable $v with type int is redundant, the variable already has this type.', + 419, + ], + [ + 'PHPDoc tag @var with type int is redundant, the expression already has this type.', + 491, + ], + [ + 'PHPDoc tag @var for variable $x with type int is redundant, the variable already has this type.', + 498, + ], + ]); + } + + public function testBug5308(): void + { + // the tags widen 'ab' to string + $this->analyse([__DIR__ . '/data/bug-5308.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + return array_merge(parent::getAdditionalConfigFiles(), [ + __DIR__ . '/redundant-var-tag.neon', + ]); + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-5308.php b/tests/PHPStan/Rules/PhpDoc/data/bug-5308.php new file mode 100644 index 00000000000..00fa52446de --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-5308.php @@ -0,0 +1,22 @@ += 8.0 + +namespace RedundantVarTag; + +class Foo +{ + + public function getString(): string + { + return ''; + } + + /** @return list */ + public function getList(): array + { + return []; + } + + /** @return array */ + public function getConfig(): array + { + return []; + } + + /** @return mixed */ + public function getExplicitMixed() + { + return null; + } + + public function getImplicitMixed() + { + return null; + } + + public function doAssign(): void + { + /** @var string $a */ + $a = $this->getString(); + + /** @var string */ + $b = $this->getString(); + + /** @phpstan-var string $c */ + $c = $this->getString(); + + /** @var non-empty-string $d */ + $d = $this->getString(); + + /** @var string $e */ + $e = 'foo'; + + /** @var list $f */ + $f = []; + + /** @var list $g */ + $g = $this->getList(); + + /** @var array $h */ + $h = $this->getConfig(); + + /** @var mixed $i */ + $i = $this->getExplicitMixed(); + + /** @var mixed $j */ + $j = $this->getImplicitMixed(); + + /** @var string $a */ + $a .= $this->getString(); + + /** @var int $k */ + [$k, $l] = $this->getList(); + + /** @var string $m */ + $m = &$a; + } + + public function doForeach(): void + { + $list = $this->getList(); + + /** @var int $value */ + foreach ($list as $value) { + } + + /** @var int<0, max> $key */ + foreach ($list as $key => $value) { + } + + /** @var list $list */ + foreach ($list as $value) { + } + + /** @var positive-int $value */ + foreach ($list as $value) { + } + + /** @var list $list */ + foreach ($list as $value) { + } + } + + public function doStatic(): void + { + /** @var null $a */ + static $a = null; + } + + public function doLoop(): void + { + $n = 0; + while ($this->getString() !== '') { + /** @var int $n */ + $n = $n + 1; + } + } + + public function doStatement(string $s): void + { + /** @var string $s */ + echo $s; + + /** @var non-empty-string $s */ + echo $s; + } + + public function doReturn(): string + { + /** @var string */ + return $this->getString(); + } + + public function doReturnNarrowed(): string + { + /** @var non-empty-string */ + return $this->getString(); + } + + public function doThrow(\Exception $e): void + { + /** @var \Exception */ + throw $e; + } + + public function doNewGeneric(): void + { + /** @var Box $box */ + $box = new Box(); + } + +} + +/** @template T */ +class Box +{ + +} + +trait FooTrait +{ + + public function doTrait(): void + { + /** @var string $a */ + $a = $this->getValue(); + } + +} + +class UsesTraitWithString +{ + + use FooTrait; + + public function getValue(): string + { + return ''; + } + +} + +class UsesTraitWithMixed +{ + + use FooTrait; + + /** @return mixed */ + public function getValue() + { + return ''; + } + +} + +interface Container +{ + + public function get(string $id): object; + +} + +class ReviewCases +{ + + /** @var array */ + private array $arr = []; + + /** @var int */ + const BAR = 1; + + public function __construct(private Container $container) + { + } + + /** @return non-empty-string */ + public function returnOffset(): string + { + /** @var non-empty-string */ + return $this->arr['a']; + } + + public function returnOffsetUnchanged(): string + { + /** @var string */ + return $this->arr['a']; + } + + /** @return non-empty-string */ + public function returnOffsetNarrowed(): string + { + if ($this->arr['a'] !== '') { + /** @var non-empty-string */ + return $this->arr['a']; + } + + return 'a'; + } + + /** + * @template T of object + * @param class-string $class + * @return T + */ + public function templateType(string $class): object + { + /** @var T $service */ + $service = $this->container->get($class); + return $service; + } + + /** @param int|string $key */ + public function benevolentUnion($key): void + { + /** @var array-key $k */ + $k = $key; + } + + /** @return list */ + public function getList(): array + { + return []; + } + + /** @return list */ + public function getExplicitMixedList(): array + { + return []; + } + + /** @return array{0?: int, 1?: int} */ + public function getMaybeList(): array + { + return []; + } + + public function overridingTag(): void + { + /** + * @var array $a + * @phpstan-var list $a + */ + $a = $this->getList(); + } + + public function implicitMixedTag(): void + { + /** @var list $d */ + $d = $this->getExplicitMixedList(); + } + + public function listness(): void + { + /** @var list{0?: int, 1?: int} $x */ + $x = $this->getMaybeList(); + } + + public function narrowedConstant(): void + { + if (static::BAR === 1) { + /** @var int $x */ + $x = static::BAR; + } + } + + public function foreachInLoop(): void + { + $items = [1]; + while (rand(0, 1) === 1) { + /** @var int $item */ + foreach ($items as $item) { + $items[] = $item; + } + } + } + + public function secondVariable(): void + { + $m = 0; + while (rand(0, 1) === 1) { + $k = $m + 1; + /** @var int $m */ + $m = $k; + } + } + + public function reference(): void + { + $a = 0; + while (rand(0, 1) === 1) { + /** @var int $m */ + $m = &$a; + $m++; + } + } + + public function byRefClosure(): void + { + $count = 0; + $f = function () use (&$count): void { + /** @var int $count */ + $count = $count + 1; + }; + $f(); + } + + public function staticVariable(): int + { + static $last = 0; + /** @var int $id */ + $id = $last + 1; + $last = $id; + return $id; + } + + /** @param list $list */ + public function taggedIteratee(array $list): void + { + /** + * @var list $list + * @var int $value + */ + foreach ($list as $value) { + } + } + + public function arrayLiteralIteratee(int $a, int|string $b): void + { + /** @var int */ + foreach ([$a, $b] as $v) { + } + } + + public function maybeDefinedIteratee(bool $b): void + { + if ($b) { + $list = $this->getList(); + } + + /** @var list $list */ + foreach ($list as $v) { + } + } + + public function maybeDefinedStatement(bool $b): void + { + if ($b) { + $x = $this->getList(); + } + + /** @var list $x */ + echo 'x'; + } + + /** @return array{int, string} */ + public function pair(): array + { + return [1, 'a']; + } + + public function destructureIntoExisting(): void + { + $a = null; + /** @var int $a */ + [$a, $b] = $this->pair(); + } + + /** @return array */ + public function map(): array + { + return []; + } + + public function twoTags(): void + { + /** + * @var int $k + * @var int $v + */ + foreach ($this->map() as $k => $v) { + } + } + + public function closureTemplateFrame(): void + { + $box = new Box(); + $f = function () use ($box): void { + /** @var Box $b */ + $b = $box; + }; + $f(); + } + +} + +class MoreCases +{ + + /** @return non-empty-list */ + public function getNonEmptyList(): array + { + return [1]; + } + + /** @return array-key */ + public function getArrayKey() + { + return 1; + } + + public function getInt(): int + { + return 1; + } + + public function wideningNonEmpty(): void + { + /** @var list $x */ + $x = $this->getNonEmptyList(); + } + + public function iterateeAlreadyNonEmpty(): void + { + $list = $this->getNonEmptyList(); + /** @var list $list */ + foreach ($list as $v) { + } + } + + public function plainUnionOverBenevolent(): void + { + /** @var int|string $k */ + $k = $this->getArrayKey(); + } + + public function unresolvableType(): void + { + /** @var self::NOPE $x */ + $x = self::NOPE; + } + + public function namedAndVariableLess(): void + { + /** @var int $x */ + /** @var string */ + $x = $this->getInt(); + } + + public function returnAssign(): int + { + /** @var int */ + return $x = $this->getInt(); + } + + public function commentBeforeDoc(): void + { + // the doc comment is not the only comment + /** @var int $x */ + $x = $this->getInt(); + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/redundant-var-tag.neon b/tests/PHPStan/Rules/PhpDoc/redundant-var-tag.neon new file mode 100644 index 00000000000..f17d1a4d3ba --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/redundant-var-tag.neon @@ -0,0 +1,2 @@ +parameters: + reportRedundantVarTag: true diff --git a/turbo-ext/src/ForeachHandler.cpp b/turbo-ext/src/ForeachHandler.cpp index 93d6fd6990c..7f9befaaf22 100644 --- a/turbo-ext/src/ForeachHandler.cpp +++ b/turbo-ext/src/ForeachHandler.cpp @@ -1004,6 +1004,12 @@ class ForeachHandler return pt_var_annotation_processor_process_var_annotation(OBJ_PROP_NUM(self, slots::varAnnotationProcessor), scope, names.raw(), stmt, NULL); } + /* $this->varAnnotationProcessor->processForeachIterateeVarAnnotation($scope, $name, $stmt) */ + zv::Val processForeachIterateeVarAnnotation(zval *scope, zend_string *name, zval *stmt) const + { + return pt_var_annotation_processor_process_foreach_iteratee_var_annotation(OBJ_PROP_NUM(self, slots::varAnnotationProcessor), scope, name, stmt); + } + /* static function () use ($condResult, $emptyArrayType): Type — captures: * $condResult, $emptyArrayType */ static void nonEmptyIterateeTypeBody(zval *captures, uint32_t argc, zval *argv, zval *return_value) @@ -1240,7 +1246,7 @@ class ForeachHandler zv::Val scope = zv::Val::copyOf(zv::Ref(scopeArg)); zval *iteratee = parts.expr.raw(); if (parts.exprName != NULL) { - scope = processVarAnnotation(scope.raw(), parts.exprName, stmt); + scope = processForeachIterateeVarAnnotation(scope.raw(), parts.exprName, stmt); if (UNEXPECTED(scope.isUndef())) return zv::Val(); } diff --git a/turbo-ext/src/NodeScopeResolver.cpp b/turbo-ext/src/NodeScopeResolver.cpp index b2d0b6b4a62..ae0b3bae0a4 100644 --- a/turbo-ext/src/NodeScopeResolver.cpp +++ b/turbo-ext/src/NodeScopeResolver.cpp @@ -1803,6 +1803,9 @@ class NodeScopeResolver zv::Val stmtResult = pt_stmt_handler_process(stmtHandler.raw(), &thisZv, stmt, scope.raw(), storage, nodeCallback, context); if (UNEXPECTED(stmtResult.isUndef())) return zv::Val(); if (!varTagDependencies.isNull()) { + zval *statementsHandler = requireStatementsHandler(); + if (UNEXPECTED(statementsHandler == NULL)) return zv::Val(); + if (UNEXPECTED(!pt_statements_handler_emit_redundant_var_tag_nodes(statementsHandler, &thisZv, scope.raw(), storage, stmt, nodeCallback))) return zv::Val(); if (UNEXPECTED(Z_TYPE_P(stmtResult.raw()) != IS_OBJECT)) { zend_throw_error(NULL, "Call to a member function getDependencies() on %s", zend_zval_value_name(stmtResult.raw())); return zv::Val(); diff --git a/turbo-ext/src/StatementsHandler.cpp b/turbo-ext/src/StatementsHandler.cpp index 656fbaefae6..768335a203b 100644 --- a/turbo-ext/src/StatementsHandler.cpp +++ b/turbo-ext/src/StatementsHandler.cpp @@ -70,6 +70,9 @@ pt_method_site pt_sh_var_tag_get_type_site; pt_method_site pt_sh_comment_get_text_site; pt_method_site pt_sh_function_get_name_site; pt_method_site pt_sh_stats_increment_site; +pt_method_site pt_sh_record_variable_site; +pt_method_site pt_sh_record_expression_site; +pt_method_site pt_sh_emit_nodes_site; pt_property_site pt_sh_name_site; pt_property_site pt_sh_expr_site; pt_property_site pt_sh_var_site; @@ -482,7 +485,7 @@ class StatementsHandler explicit StatementsHandler(zend_object *self) : self(self) {} /* the constructor body: the promoted properties, then the twin's body */ - void construct(zval *fileTypeMapper, zval *templateArgumentObserver, zval *templateArgumentResolver, zval *container, zval *staticVariableInference, bool unresolvedTemplateArguments) + void construct(zval *fileTypeMapper, zval *templateArgumentObserver, zval *templateArgumentResolver, zval *container, zval *staticVariableInference, zval *redundantVarTagDetector, bool unresolvedTemplateArguments, bool reportRedundantVarTag) { zv::ObjRef object(self); object.propAtWrite(slots::fileTypeMapper, zv::Val::copyOf(zv::Ref(fileTypeMapper))); @@ -490,7 +493,9 @@ class StatementsHandler object.propAtWrite(slots::templateArgumentResolver, zv::Val::copyOf(zv::Ref(templateArgumentResolver))); object.propAtWrite(slots::container, zv::Val::copyOf(zv::Ref(container))); object.propAtWrite(slots::staticVariableInference, zv::Val::copyOf(zv::Ref(staticVariableInference))); + object.propAtWrite(slots::redundantVarTagDetector, zv::Val::copyOf(zv::Ref(redundantVarTagDetector))); object.propAtWrite(slots::unresolvedTemplateArguments, zv::Val::boolean(unresolvedTemplateArguments)); + object.propAtWrite(slots::reportRedundantVarTag, zv::Val::boolean(reportRedundantVarTag)); const char *debug = getenv("PHPSTAN_TEMPLATE_ARGUMENTS_DEBUG"); object.propAtWrite(slots::debugTemplateArguments, zv::Val::boolean(debug != NULL && strcmp(debug, "1") == 0)); } @@ -1154,6 +1159,16 @@ class StatementsHandler if (UNEXPECTED(scope.isUndef())) return zv::Val(); } + if (reportsRedundantVarTags()) { + zend_long certaintyYes = pt_type_trinary_value(certainty.raw()); + if (UNEXPECTED(certaintyYes < 0)) return zv::Val(); + zval defines; + ZVAL_BOOL(&defines, certaintyYes == PT_TRI_YES); + zv::Args recordArgv{stmt, &nameZv, varTag, scope.raw(), &nameZv, &defines}; + zv::Val recorded = callRedundantVarTagDetector(pt_sh_record_variable_site, PT_LC("recordvariable"), 6, recordArgv); + if (UNEXPECTED(recorded.isUndef())) return zv::Val(); + } + zv::Val nativeScope = pt_mutating_scope_do_not_treat_phpdoc_types_as_certain(Z_OBJ_P(scope.raw())); if (UNEXPECTED(nativeScope.isUndef())) return zv::Val(); zv::Val assignedType = tagGetType(varTag); @@ -1192,6 +1207,11 @@ class StatementsHandler zv::Val changedNode = pt_type_new(PT_CLASS_VAR_TAG_CHANGED_EXPRESSION_TYPE_NODE, 2, nodeArgv); if (UNEXPECTED(changedNode.isUndef())) return zv::Val(); if (UNEXPECTED(!pt_node_scope_resolver_call_node_callback(nodeScopeResolver, nodeCallback, changedNode.raw(), scope, storage))) return zv::Val(); + if (reportsRedundantVarTags()) { + zv::Args recordArgv{nodeScopeResolver, stmt, varTag.raw(), defaultExpr, scope}; + zv::Val recorded = callRedundantVarTagDetector(pt_sh_record_expression_site, PT_LC("recordexpression"), 5, recordArgv); + if (UNEXPECTED(recorded.isUndef())) return zv::Val(); + } zv::Val defaultExprResult = pt_expression_result_storage_find(storage, defaultExpr); if (UNEXPECTED(defaultExprResult.isUndef())) return zv::Val(); if (defaultExprResult.isNull()) return templateArgumentConstraintsCreateEmpty(); @@ -1208,9 +1228,36 @@ class StatementsHandler return templateArgumentObserverCollectSend(OBJ_PROP_NUM(self, slots::templateArgumentObserver), declared.raw(), actual.raw()); } + /* Mirrors emitRedundantVarTagNodes(); the detector stays PHP - it runs + * only with the opt-in parameter. false = pending exception */ + [[nodiscard]] bool emitRedundantVarTagNodes(zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *nodeCallback) const + { + if (!reportsRedundantVarTags()) return true; + zv::Args argv{nodeScopeResolver, scope, storage, stmt, nodeCallback}; + return !callRedundantVarTagDetector(pt_sh_emit_nodes_site, PT_LC("emitnodes"), 5, argv).isUndef(); + } + private: zend_object *self; + /* $this->reportRedundantVarTag */ + bool reportsRedundantVarTags() const + { + return Z_TYPE_P(OBJ_PROP_NUM(self, slots::reportRedundantVarTag)) == IS_TRUE; + } + + /* $this->redundantVarTagDetector->$method(...$argv); UNDEF = pending + * exception */ + zv::Val callRedundantVarTagDetector(pt_method_site &site, const char *lcname, size_t len, uint32_t argc, zval *argv) const + { + zval *detector = OBJ_PROP_NUM(self, slots::redundantVarTagDetector); + if (UNEXPECTED(Z_TYPE_P(detector) != IS_OBJECT)) { + zend_throw_error(NULL, "Typed property %s::$redundantVarTagDetector must not be accessed before initialization", ZSTR_VAL(self->ce->name)); + return zv::Val(); + } + return pt_call_method_cached(site, Z_OBJ_P(detector), lcname, len, argc, argv); + } + /* {{{ statement lists */ /* $node instanceof Function_ || ClassLike || Label */ @@ -3541,6 +3588,13 @@ zv::Val pt_statements_handler_emit_var_tag_changed_node(zval *handler, zval *nod return pt_type_call(Z_OBJ_P(handler), PT_LC("emitvartagchangednode"), 6, argv); } +bool pt_statements_handler_emit_redundant_var_tag_nodes(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *nodeCallback) +{ + if (isNativeHandler(handler)) return StatementsHandler(Z_OBJ_P(handler)).emitRedundantVarTagNodes(nodeScopeResolver, scope, storage, stmt, nodeCallback); + zv::Args argv{nodeScopeResolver, scope, storage, stmt, nodeCallback}; + return !pt_type_call(Z_OBJ_P(handler), PT_LC("emitredundantvartagnodes"), 5, argv).isUndef(); +} + zv::Val pt_statements_handler_get_variable_mention_flow(zval *handler, zval *stmt) { if (isNativeHandler(handler)) return StatementsHandler(Z_OBJ_P(handler)).getVariableMentionFlow(stmt); @@ -3566,17 +3620,19 @@ 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, *staticVariableInference; - bool unresolvedTemplateArguments; - ZEND_PARSE_PARAMETERS_START(6, 6) + zval *fileTypeMapper, *templateArgumentObserver, *templateArgumentResolver, *container, *staticVariableInference, *redundantVarTagDetector; + bool unresolvedTemplateArguments, reportRedundantVarTag; + ZEND_PARSE_PARAMETERS_START(8, 8) Z_PARAM_OBJECT(fileTypeMapper) Z_PARAM_OBJECT(templateArgumentObserver) Z_PARAM_OBJECT(templateArgumentResolver) Z_PARAM_OBJECT(container) Z_PARAM_OBJECT(staticVariableInference) + Z_PARAM_OBJECT(redundantVarTagDetector) Z_PARAM_BOOL(unresolvedTemplateArguments) + Z_PARAM_BOOL(reportRedundantVarTag) ZEND_PARSE_PARAMETERS_END(); - StatementsHandler(Z_OBJ_P(ZEND_THIS)).construct(fileTypeMapper, templateArgumentObserver, templateArgumentResolver, container, staticVariableInference, unresolvedTemplateArguments); + StatementsHandler(Z_OBJ_P(ZEND_THIS)).construct(fileTypeMapper, templateArgumentObserver, templateArgumentResolver, container, staticVariableInference, redundantVarTagDetector, unresolvedTemplateArguments, reportRedundantVarTag); }); cls.method(sigs::processNodesWithStorage, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -3689,6 +3745,18 @@ PT_MINIT_REGISTRATION(pt_register_statements_handler) PT_RETURN_VAL(StatementsHandler(Z_OBJ_P(ZEND_THIS)).emitVarTagChangedNode(nodeScopeResolver, scope, storage, stmt, defaultExpr, nodeCallback)); }); + cls.method(sigs::emitRedundantVarTagNodes, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *nodeScopeResolver, *scope, *storage, *stmt, *nodeCallback; + ZEND_PARSE_PARAMETERS_START(5, 5) + Z_PARAM_OBJECT(nodeScopeResolver) + Z_PARAM_OBJECT(scope) + Z_PARAM_OBJECT(storage) + Z_PARAM_OBJECT(stmt) + Z_PARAM_ZVAL(nodeCallback) + ZEND_PARSE_PARAMETERS_END(); + if (!StatementsHandler(Z_OBJ_P(ZEND_THIS)).emitRedundantVarTagNodes(nodeScopeResolver, scope, storage, stmt, nodeCallback)) RETURN_THROWS(); + }); + cls.shadow(&pt_ce_statements_handler); } diff --git a/turbo-ext/src/VarAnnotationProcessor.cpp b/turbo-ext/src/VarAnnotationProcessor.cpp index 6ff7594b11c..84a4490b33e 100644 --- a/turbo-ext/src/VarAnnotationProcessor.cpp +++ b/turbo-ext/src/VarAnnotationProcessor.cpp @@ -35,6 +35,7 @@ pt_method_site pt_vap_function_get_name_site; pt_method_site pt_vap_get_text_site; pt_method_site pt_vap_get_resolved_php_doc_site; pt_method_site pt_vap_var_tag_get_type_site; +pt_method_site pt_vap_record_variable_site; /* $object->method(...$argv) of a value the twin calls a method on: the * engine's Error for a non-object; UNDEF = pending exception */ @@ -89,16 +90,40 @@ class VarAnnotationProcessor public: explicit VarAnnotationProcessor(zend_object *self) : self(self) {} - /* Mirrors __construct(): the promoted property */ - static void construct(zend_object *object, zval *fileTypeMapper) + /* Mirrors __construct(): the promoted properties */ + static void construct(zend_object *object, zval *fileTypeMapper, zval *redundantVarTagDetector, bool reportRedundantVarTag) { pt_write_slot(object, slots::fileTypeMapper, fileTypeMapper); + pt_write_slot(object, slots::redundantVarTagDetector, redundantVarTagDetector); + zval report; + ZVAL_BOOL(&report, reportRedundantVarTag); + pt_write_slot(object, slots::reportRedundantVarTag, &report); } - /* Mirrors processVarAnnotation(); the by-reference $changed as the + /* Mirrors processVarAnnotation(): the private applyVarTags() for a + * statement's variables */ + zv::Val processVarAnnotation(zval *scope, HashTable *variableNames, zval *node, zval *changedRef, bool *changedFlag) const + { + return applyVarTags(scope, variableNames, node, changedRef, changedFlag, false); + } + + /* Mirrors processForeachIterateeVarAnnotation(): the private + * applyVarTags() for the variable a foreach iterates, on entering the + * loop body */ + zv::Val processForeachIterateeVarAnnotation(zval *scope, zend_string *variableName, zval *node) const + { + zv::Arr names = zv::Arr::create(1); + names.push(zv::Val::string(variableName)); + return applyVarTags(scope, names.table(), node, NULL, NULL, true); + } + +private: + zend_object *self; + + /* Mirrors the private applyVarTags(); the by-reference $changed as the * caller's reference (NULL when not passed) and/or a native flag (NULL * when not wanted). The resulting scope, UNDEF = pending exception */ - zv::Val processVarAnnotation(zval *scope, HashTable *variableNames, zval *node, zval *changedRef, bool *changedFlag) const + zv::Val applyVarTags(zval *scope, HashTable *variableNames, zval *node, zval *changedRef, bool *changedFlag, bool narrowedByForeach) const { zv::Val function = pt_mutating_scope_get_function(Z_OBJ_P(scope)); if (UNEXPECTED(function.isUndef())) return zv::Val(); @@ -196,7 +221,7 @@ class VarAnnotationProcessor zval *variableName = entry.value().deref().raw(); zval *varTag = findKey(tags, variableName); if (varTag == NULL) continue; - if (UNEXPECTED(!assign(result, variableName, varTag, changedRef, changedFlag))) return zv::Val(); + if (UNEXPECTED(!assign(result, node, variableName, variableName, varTag, changedRef, changedFlag, narrowedByForeach))) return zv::Val(); } if (zend_hash_num_elements(variableNames) == 1 && zend_hash_num_elements(tags) == 1) { @@ -213,15 +238,31 @@ class VarAnnotationProcessor } else { ZVAL_DEREF(variableName); } - if (UNEXPECTED(!assign(result, variableName, varTag, changedRef, changedFlag))) return zv::Val(); + zval key; + ZVAL_LONG(&key, 0); + if (UNEXPECTED(!assign(result, node, &key, variableName, varTag, changedRef, changedFlag, narrowedByForeach))) return zv::Val(); } } return result; } -private: - zend_object *self; + /* if ($this->reportRedundantVarTag) { + * $this->redundantVarTagDetector->recordVariable($node, $key, $varTag, + * $scope, $variableName, true, $narrowedByForeach); + * } - the detector stays PHP: it runs only with the opt-in parameter. + * false = pending exception */ + [[nodiscard]] bool recordVariable(zval *node, zval *key, zval *varTag, zv::Val &scope, zval *variableName, bool narrowedByForeach) const + { + if (Z_TYPE_P(OBJ_PROP_NUM(self, slots::reportRedundantVarTag)) != IS_TRUE) return true; + zval *detector = OBJ_PROP_NUM(self, slots::redundantVarTagDetector); + zval defines, narrowed; + ZVAL_TRUE(&defines); + ZVAL_BOOL(&narrowed, narrowedByForeach); + zv::Args args{node, key, varTag, scope.raw(), variableName, &defines, &narrowed}; + zv::Val recorded = callOn(pt_vap_record_variable_site, detector, PT_LC("recordvariable"), "recordVariable", 7, args); + return !recorded.isUndef(); + } /* $reflection->getName() of a class or trait reflection */ static zv::Val reflectionName(zval *reflection) @@ -233,10 +274,11 @@ class VarAnnotationProcessor return pt_class_reflection_get_name(Z_OBJ_P(reflection)); } - /* $variableType = $varTag->getType(); $changed = true; - * $scope = $scope->assignVariable($variableName, $variableType, new - * MixedType(), TrinaryLogic::createYes()); false = pending exception */ - [[nodiscard]] static bool assign(zv::Val &scope, zval *variableName, zval *varTag, zval *changedRef, bool *changedFlag) + /* $variableType = $varTag->getType(); $changed = true; the tag's + * recordVariable() under $key; $scope = $scope->assignVariable( + * $variableName, $variableType, new MixedType(), + * TrinaryLogic::createYes()); false = pending exception */ + [[nodiscard]] bool assign(zv::Val &scope, zval *node, zval *key, zval *variableName, zval *varTag, zval *changedRef, bool *changedFlag, bool narrowedByForeach) const { zv::Val variableType = callOn(pt_vap_var_tag_get_type_site, varTag, PT_LC("gettype"), "getType"); if (UNEXPECTED(variableType.isUndef())) return false; @@ -247,6 +289,7 @@ class VarAnnotationProcessor ZEND_TRY_ASSIGN_REF_TRUE(changedRef); if (UNEXPECTED(EG(exception))) return false; } + if (UNEXPECTED(!recordVariable(node, key, varTag, scope, variableName, narrowedByForeach))) return false; zv::Val mixed = pt_type_new_mixed_type(); if (UNEXPECTED(mixed.isUndef())) return false; zv::Val next; @@ -292,6 +335,15 @@ zv::Val pt_var_annotation_processor_process_var_annotation(zval *processor, zval return result; } +zv::Val pt_var_annotation_processor_process_foreach_iteratee_var_annotation(zval *processor, zval *scope, zend_string *variableName, zval *node) +{ + if (EXPECTED(Z_OBJCE_P(processor) == pt_ce_var_annotation_processor)) { + return VarAnnotationProcessor(Z_OBJ_P(processor)).processForeachIterateeVarAnnotation(scope, variableName, node); + } + zv::Args args{scope, variableName, node}; + return pt_type_call(Z_OBJ_P(processor), PT_LC("processforeachiterateevarannotation"), 3, args); +} + /* }}} */ /* {{{ engine ABI glue: parameter parsing + registration */ @@ -307,9 +359,10 @@ PT_MINIT_REGISTRATION(pt_register_var_annotation_processor) /* the DI service's constructor: the generated arginfo names the twin's * parameter class exactly */ cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *fileTypeMapper; - if (!zp::parse(execute_data, fileTypeMapper)) RETURN_THROWS(); - VarAnnotationProcessor::construct(Z_OBJ_P(ZEND_THIS), fileTypeMapper); + zval *fileTypeMapper, *redundantVarTagDetector; + bool reportRedundantVarTag; + if (!zp::parse(execute_data, fileTypeMapper, redundantVarTagDetector, reportRedundantVarTag)) RETURN_THROWS(); + VarAnnotationProcessor::construct(Z_OBJ_P(ZEND_THIS), fileTypeMapper, redundantVarTagDetector, reportRedundantVarTag); }); cls.method(sigs::processVarAnnotation, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -325,6 +378,17 @@ PT_MINIT_REGISTRATION(pt_register_var_annotation_processor) PT_RETURN_VAL(VarAnnotationProcessor(Z_OBJ_P(ZEND_THIS)).processVarAnnotation(scope, variableNames, node, changed != NULL && Z_ISREF_P(changed) ? changed : NULL, NULL)); }); + cls.method(sigs::processForeachIterateeVarAnnotation, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *scope, *node; + zend_string *variableName; + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_OBJECT(scope) + Z_PARAM_STR(variableName) + Z_PARAM_OBJECT(node) + ZEND_PARSE_PARAMETERS_END(); + PT_RETURN_VAL(VarAnnotationProcessor(Z_OBJ_P(ZEND_THIS)).processForeachIterateeVarAnnotation(scope, variableName, node)); + }); + cls.shadow(&pt_ce_var_annotation_processor); } diff --git a/turbo-ext/src/generated/StatementsHandler.h b/turbo-ext/src/generated/StatementsHandler.h index 08f4e1b1eed..f67dd18410b 100644 --- a/turbo-ext/src/generated/StatementsHandler.h +++ b/turbo-ext/src/generated/StatementsHandler.h @@ -16,7 +16,9 @@ inline constexpr uint32_t templateArgumentObserver = 2; inline constexpr uint32_t templateArgumentResolver = 3; inline constexpr uint32_t container = 4; inline constexpr uint32_t staticVariableInference = 5; -inline constexpr uint32_t unresolvedTemplateArguments = 6; +inline constexpr uint32_t redundantVarTagDetector = 6; +inline constexpr uint32_t unresolvedTemplateArguments = 7; +inline constexpr uint32_t reportRedundantVarTag = 8; } // namespace slot inline void declareClass(reg::Class &cls) @@ -33,7 +35,9 @@ inline void declareProperties(reg::Class &cls) 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("redundantVarTagDetector", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\RedundantVarTagDetector"); cls.property("unresolvedTemplateArguments", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); + cls.property("reportRedundantVarTag", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -49,397 +53,410 @@ inline constexpr char strings[] = "PHPStan\\DependencyInjection\\Container\0" /* 205 */ "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 */ - "PHPStan\\Dependency\\Dependencies\0" /* 532 */ - "processNodesWithStorage\0" /* 564 */ - "parentNode\0" /* 588 */ - "PhpParser\\Node\0" /* 599 */ - "bodyStmts\0" /* 614 */ - "storage\0" /* 624 */ - "context\0" /* 632 */ - "PHPStan\\Analyser\\StatementContext\0" /* 640 */ - "gotoNameMatcher\0" /* 674 */ - "Closure\0" /* 690 */ - "mergeBodyScopeEachIteration\0" /* 698 */ - "resolveBackwardGotoScope\0" /* 726 */ - "labelName\0" /* 751 */ - "alreadyTerminated\0" /* 761 */ - "exitPoints\0" /* 779 */ - "mergeForwardGotoExitPoints\0" /* 790 */ - "nextStmts\0" /* 817 */ - "processUnreachableStatement\0" /* 827 */ - "earlyBinding\0" /* 855 */ - "getNextUnreachableStatements\0" /* 868 */ - "stmts\0" /* 897 */ - "PHPStan\\Analyser\\InternalStatementResult\0" /* 903 */ - "doProcessStmtNodes\0" /* 944 */ - "previous\0" /* 963 */ - "record\0" /* 972 */ - "processLoopPassStmtNodes\0" /* 979 */ - "lastPass\0" /* 1004 */ - "processLoopFinalStmtNodes\0" /* 1013 */ - "replayTarget\0" /* 1039 */ - "finalWalk\0" /* 1052 */ - "walkAgainstPreviousPass\0" /* 1062 */ - "variableNames\0" /* 1086 */ - "withByRefClosureHolders\0" /* 1100 */ - "state\0" /* 1124 */ - "PHPStan\\Analyser\\StatementListWalkState\0" /* 1130 */ - "exitPointCount\0" /* 1170 */ - "throwPointCount\0" /* 1185 */ - "recordedEntry\0" /* 1201 */ - "rebaseAppendedPoints\0" /* 1215 */ - "i\0" /* 1236 */ - "stmt\0" /* 1238 */ - "PhpParser\\Node\\Stmt\0" /* 1243 */ - "shouldCheckLastStatement\0" /* 1263 */ - "processStatementStep\0" /* 1288 */ - "PHPStan\\Analyser\\ExpressionResult\0" /* 1309 */ - "readEndStatementExprResult\0" /* 1343 */ - "processBodyStmtNodesTwoPass\0" /* 1370 */ - "staticSites\0" /* 1398 */ - "staticStatementIndexes\0" /* 1410 */ - "parentFrame\0" /* 1433 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1445 */ - "entries\0" /* 1493 */ - "recording\0" /* 1501 */ - "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1511 */ - "inferStaticVariableTypes\0" /* 1550 */ - "staticWalk\0" /* 1575 */ - "frame\0" /* 1586 */ - "parentConstraints\0" /* 1592 */ - "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1610 */ - "replayStaticVariableWalk\0" /* 1664 */ - "bodyScope\0" /* 1689 */ - "endState\0" /* 1699 */ - "moreScopes\0" /* 1708 */ - "collectStaticVariableScopes\0" /* 1719 */ - "collectStaticVariableStateScopes\0" /* 1747 */ - "names\0" /* 1780 */ - "scopes\0" /* 1786 */ - "collectStaticVariableTypes\0" /* 1793 */ - "runs\0" /* 1820 */ - "types\0" /* 1825 */ - "walkedConditionalExpressions\0" /* 1831 */ - "enteredTypes\0" /* 1860 */ - "collectStaticVariableConditionalExpressions\0" /* 1873 */ - "isWalkedStaticVariableState\0" /* 1917 */ - "a\0" /* 1945 */ - "b\0" /* 1947 */ - "equalStaticVariableStates\0" /* 1949 */ - "equalStaticVariableConditionalExpressions\0" /* 1975 */ - "PHPStan\\Analyser\\ConditionalExpressionHolder\0" /* 2017 */ - "equalConditionalExpressionHolders\0" /* 2062 */ - "walkTypes\0" /* 2096 */ - "generalize\0" /* 2106 */ - "joinStaticVariableTypes\0" /* 2117 */ - "PHPStan\\Analyser\\ClosureProcessor\0" /* 2141 */ - "getClosureProcessor\0" /* 2175 */ - "processDeferredByRefClosureBodies\0" /* 2195 */ - "statementStartTokenPositions\0" /* 2229 */ - "observeClosureSignatures\0" /* 2258 */ - "recordedExit\0" /* 2283 */ - "withRecordedConstraints\0" /* 2296 */ - "containsLabels\0" /* 2320 */ - "from\0" /* 2335 */ - "to\0" /* 2340 */ - "appendRecordedStatementResults\0" /* 2343 */ - "PHPStan\\Analyser\\VariableFlow\0" /* 2374 */ - "getVariableMentionFlow\0" /* 2404 */ - "statementMentionsAnyVariable\0" /* 2427 */ - "node\0" /* 2456 */ - "mentionsEverything\0" /* 2461 */ - "collectMentionedVariables\0" /* 2480 */ - "getVarTagDependencies\0" /* 2506 */ - "statement\0" /* 2528 */ - "getOverridingThrowPoints\0" /* 2538 */ - "defaultExpr\0" /* 2563 */ - "PhpParser\\Node\\Expr\0" /* 2575 */ - "processStmtVarAnnotation\0" /* 2595 */ - "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 2620 */ - "findSingleVariableLessVarTag\0" /* 2646 */ - "emitVarTagChangedNode"; /* 2675 */ + "redundantVarTagDetector\0" /* 317 */ + "PHPStan\\Analyser\\RedundantVarTagDetector\0" /* 341 */ + "unresolvedTemplateArguments\0" /* 382 */ + "reportRedundantVarTag\0" /* 410 */ + "__construct\0" /* 432 */ + "nodeScopeResolver\0" /* 444 */ + "PHPStan\\Analyser\\NodeScopeResolver\0" /* 462 */ + "nodes\0" /* 497 */ + "scope\0" /* 503 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 509 */ + "expressionResultStorage\0" /* 540 */ + "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 564 */ + "nodeCallback\0" /* 605 */ + "\0" /* 618 */ + "PHPStan\\Dependency\\Dependencies\0" /* 619 */ + "processNodesWithStorage\0" /* 651 */ + "parentNode\0" /* 675 */ + "PhpParser\\Node\0" /* 686 */ + "bodyStmts\0" /* 701 */ + "storage\0" /* 711 */ + "context\0" /* 719 */ + "PHPStan\\Analyser\\StatementContext\0" /* 727 */ + "gotoNameMatcher\0" /* 761 */ + "Closure\0" /* 777 */ + "mergeBodyScopeEachIteration\0" /* 785 */ + "resolveBackwardGotoScope\0" /* 813 */ + "labelName\0" /* 838 */ + "alreadyTerminated\0" /* 848 */ + "exitPoints\0" /* 866 */ + "mergeForwardGotoExitPoints\0" /* 877 */ + "nextStmts\0" /* 904 */ + "processUnreachableStatement\0" /* 914 */ + "earlyBinding\0" /* 942 */ + "getNextUnreachableStatements\0" /* 955 */ + "stmts\0" /* 984 */ + "PHPStan\\Analyser\\InternalStatementResult\0" /* 990 */ + "doProcessStmtNodes\0" /* 1031 */ + "previous\0" /* 1050 */ + "record\0" /* 1059 */ + "processLoopPassStmtNodes\0" /* 1066 */ + "lastPass\0" /* 1091 */ + "processLoopFinalStmtNodes\0" /* 1100 */ + "replayTarget\0" /* 1126 */ + "finalWalk\0" /* 1139 */ + "walkAgainstPreviousPass\0" /* 1149 */ + "variableNames\0" /* 1173 */ + "withByRefClosureHolders\0" /* 1187 */ + "state\0" /* 1211 */ + "PHPStan\\Analyser\\StatementListWalkState\0" /* 1217 */ + "exitPointCount\0" /* 1257 */ + "throwPointCount\0" /* 1272 */ + "recordedEntry\0" /* 1288 */ + "rebaseAppendedPoints\0" /* 1302 */ + "i\0" /* 1323 */ + "stmt\0" /* 1325 */ + "PhpParser\\Node\\Stmt\0" /* 1330 */ + "shouldCheckLastStatement\0" /* 1350 */ + "processStatementStep\0" /* 1375 */ + "PHPStan\\Analyser\\ExpressionResult\0" /* 1396 */ + "readEndStatementExprResult\0" /* 1430 */ + "processBodyStmtNodesTwoPass\0" /* 1457 */ + "staticSites\0" /* 1485 */ + "staticStatementIndexes\0" /* 1497 */ + "parentFrame\0" /* 1520 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame\0" /* 1532 */ + "entries\0" /* 1580 */ + "recording\0" /* 1588 */ + "PHPStan\\Analyser\\RecordingNodeCallback\0" /* 1598 */ + "inferStaticVariableTypes\0" /* 1637 */ + "staticWalk\0" /* 1662 */ + "frame\0" /* 1673 */ + "parentConstraints\0" /* 1679 */ + "PHPStan\\Analyser\\Generics\\TemplateArgumentConstraints\0" /* 1697 */ + "replayStaticVariableWalk\0" /* 1751 */ + "bodyScope\0" /* 1776 */ + "endState\0" /* 1786 */ + "moreScopes\0" /* 1795 */ + "collectStaticVariableScopes\0" /* 1806 */ + "collectStaticVariableStateScopes\0" /* 1834 */ + "names\0" /* 1867 */ + "scopes\0" /* 1873 */ + "collectStaticVariableTypes\0" /* 1880 */ + "runs\0" /* 1907 */ + "types\0" /* 1912 */ + "walkedConditionalExpressions\0" /* 1918 */ + "enteredTypes\0" /* 1947 */ + "collectStaticVariableConditionalExpressions\0" /* 1960 */ + "isWalkedStaticVariableState\0" /* 2004 */ + "a\0" /* 2032 */ + "b\0" /* 2034 */ + "equalStaticVariableStates\0" /* 2036 */ + "equalStaticVariableConditionalExpressions\0" /* 2062 */ + "PHPStan\\Analyser\\ConditionalExpressionHolder\0" /* 2104 */ + "equalConditionalExpressionHolders\0" /* 2149 */ + "walkTypes\0" /* 2183 */ + "generalize\0" /* 2193 */ + "joinStaticVariableTypes\0" /* 2204 */ + "PHPStan\\Analyser\\ClosureProcessor\0" /* 2228 */ + "getClosureProcessor\0" /* 2262 */ + "processDeferredByRefClosureBodies\0" /* 2282 */ + "statementStartTokenPositions\0" /* 2316 */ + "observeClosureSignatures\0" /* 2345 */ + "recordedExit\0" /* 2370 */ + "withRecordedConstraints\0" /* 2383 */ + "containsLabels\0" /* 2407 */ + "from\0" /* 2422 */ + "to\0" /* 2427 */ + "appendRecordedStatementResults\0" /* 2430 */ + "PHPStan\\Analyser\\VariableFlow\0" /* 2461 */ + "getVariableMentionFlow\0" /* 2491 */ + "statementMentionsAnyVariable\0" /* 2514 */ + "node\0" /* 2543 */ + "mentionsEverything\0" /* 2548 */ + "collectMentionedVariables\0" /* 2567 */ + "getVarTagDependencies\0" /* 2593 */ + "statement\0" /* 2615 */ + "getOverridingThrowPoints\0" /* 2625 */ + "defaultExpr\0" /* 2650 */ + "PhpParser\\Node\\Expr\0" /* 2662 */ + "processStmtVarAnnotation\0" /* 2682 */ + "PHPStan\\PhpDoc\\Tag\\VarTag\0" /* 2707 */ + "findSingleVariableLessVarTag\0" /* 2733 */ + "emitVarTagChangedNode\0" /* 2762 */ + "emitRedundantVarTagNodes"; /* 2784 */ 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, 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_NULL, 532), /* processNodesWithStorage return */ - reg::packed(357, 0, 375), /* resolveBackwardGotoScope $nodeScopeResolver */ - reg::packed(588, 0, 599), /* resolveBackwardGotoScope $parentNode */ - reg::packed(614, MAY_BE_ARRAY), /* resolveBackwardGotoScope $bodyStmts */ - reg::packed(416, 0, 422), /* resolveBackwardGotoScope $scope */ - reg::packed(624, 0, 477), /* resolveBackwardGotoScope $storage */ - reg::packed(632, 0, 640), /* resolveBackwardGotoScope $context */ - reg::packed(674, 0, 690), /* resolveBackwardGotoScope $gotoNameMatcher */ - reg::packed(698, MAY_BE_BOOL), /* resolveBackwardGotoScope $mergeBodyScopeEachIteration */ - reg::packed(531, 0, 422), /* resolveBackwardGotoScope return */ - reg::packed(751, MAY_BE_STRING), /* mergeForwardGotoExitPoints $labelName */ - reg::packed(416, 0, 422), /* mergeForwardGotoExitPoints $scope */ - reg::packed(761, MAY_BE_BOOL), /* mergeForwardGotoExitPoints $alreadyTerminated */ - reg::packed(779, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints $exitPoints */ - reg::packed(531, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints return */ - reg::packed(357, 0, 375), /* processUnreachableStatement $nodeScopeResolver */ - reg::packed(817, MAY_BE_ARRAY), /* processUnreachableStatement $nextStmts */ - reg::packed(416, 0, 422), /* processUnreachableStatement $scope */ - reg::packed(624, 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(855, MAY_BE_BOOL), /* getNextUnreachableStatements $earlyBinding */ - reg::packed(531, MAY_BE_ARRAY), /* getNextUnreachableStatements return */ - reg::packed(357, 0, 375), /* doProcessStmtNodes $nodeScopeResolver */ - reg::packed(588, 0, 599), /* doProcessStmtNodes $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* doProcessStmtNodes $stmts */ - reg::packed(416, 0, 422), /* doProcessStmtNodes $scope */ - reg::packed(624, 0, 477), /* doProcessStmtNodes $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* doProcessStmtNodes $nodeCallback */ - reg::packed(632, 0, 640), /* doProcessStmtNodes $context */ - reg::packed(531, 0, 903), /* doProcessStmtNodes return */ - reg::packed(357, 0, 375), /* processLoopPassStmtNodes $nodeScopeResolver */ - reg::packed(588, 0, 599), /* processLoopPassStmtNodes $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* processLoopPassStmtNodes $stmts */ - reg::packed(416, 0, 422), /* processLoopPassStmtNodes $scope */ - reg::packed(624, 0, 477), /* processLoopPassStmtNodes $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* processLoopPassStmtNodes $nodeCallback */ - reg::packed(632, 0, 640), /* processLoopPassStmtNodes $context */ - reg::packed(963, MAY_BE_NULL | MAY_BE_ARRAY), /* processLoopPassStmtNodes $previous */ - reg::packed(972, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, true, false), /* processLoopPassStmtNodes $record */ - reg::packed(531, 0, 903), /* processLoopPassStmtNodes return */ - reg::packed(357, 0, 375), /* processLoopFinalStmtNodes $nodeScopeResolver */ - reg::packed(588, 0, 599), /* processLoopFinalStmtNodes $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $stmts */ - reg::packed(416, 0, 422), /* processLoopFinalStmtNodes $scope */ - reg::packed(624, 0, 477), /* processLoopFinalStmtNodes $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* processLoopFinalStmtNodes $nodeCallback */ - reg::packed(632, 0, 640), /* processLoopFinalStmtNodes $context */ - reg::packed(1004, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $lastPass */ - reg::packed(531, 0, 903), /* processLoopFinalStmtNodes return */ - reg::packed(357, 0, 375), /* walkAgainstPreviousPass $nodeScopeResolver */ - reg::packed(588, 0, 599), /* walkAgainstPreviousPass $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* walkAgainstPreviousPass $stmts */ - reg::packed(416, 0, 422), /* walkAgainstPreviousPass $scope */ - reg::packed(624, 0, 477), /* walkAgainstPreviousPass $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* walkAgainstPreviousPass $nodeCallback */ - reg::packed(632, 0, 640), /* walkAgainstPreviousPass $context */ - reg::packed(963, MAY_BE_NULL | MAY_BE_ARRAY), /* walkAgainstPreviousPass $previous */ - reg::packed(1039, MAY_BE_NULL | MAY_BE_CALLABLE), /* walkAgainstPreviousPass $replayTarget */ - reg::packed(1052, MAY_BE_BOOL), /* walkAgainstPreviousPass $finalWalk */ - reg::packed(531, MAY_BE_ARRAY), /* walkAgainstPreviousPass return */ - reg::packed(416, 0, 422), /* withByRefClosureHolders $scope */ - reg::packed(1086, MAY_BE_ARRAY), /* withByRefClosureHolders $variableNames */ - reg::packed(531, MAY_BE_ARRAY), /* withByRefClosureHolders return */ - reg::packed(1124, 0, 1130), /* rebaseAppendedPoints $state */ - reg::packed(1170, MAY_BE_LONG), /* rebaseAppendedPoints $exitPointCount */ - reg::packed(1185, MAY_BE_LONG), /* rebaseAppendedPoints $throwPointCount */ - reg::packed(1201, 0, 422), /* rebaseAppendedPoints $recordedEntry */ - reg::packed(531, MAY_BE_VOID), /* rebaseAppendedPoints return */ - reg::packed(357, 0, 375), /* processStatementStep $nodeScopeResolver */ - reg::packed(588, 0, 599), /* processStatementStep $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* processStatementStep $stmts */ - reg::packed(1236, MAY_BE_LONG), /* processStatementStep $i */ - reg::packed(1238, 0, 1243), /* processStatementStep $stmt */ - reg::packed(1124, 0, 1130), /* processStatementStep $state */ - reg::packed(624, 0, 477), /* processStatementStep $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* processStatementStep $nodeCallback */ - reg::packed(632, 0, 640), /* processStatementStep $context */ - reg::packed(1263, MAY_BE_BOOL), /* processStatementStep $shouldCheckLastStatement */ - reg::packed(531, MAY_BE_VOID), /* processStatementStep return */ - reg::packed(1238, 0, 1243), /* readEndStatementExprResult $stmt */ - reg::packed(624, 0, 477), /* readEndStatementExprResult $storage */ - reg::packed(531, MAY_BE_NULL, 1309), /* readEndStatementExprResult return */ - reg::packed(357, 0, 375), /* processBodyStmtNodesTwoPass $nodeScopeResolver */ - reg::packed(588, 0, 599), /* processBodyStmtNodesTwoPass $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* processBodyStmtNodesTwoPass $stmts */ - reg::packed(416, 0, 422), /* processBodyStmtNodesTwoPass $scope */ - reg::packed(624, 0, 477), /* processBodyStmtNodesTwoPass $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* processBodyStmtNodesTwoPass $nodeCallback */ - reg::packed(632, 0, 640), /* processBodyStmtNodesTwoPass $context */ - reg::packed(531, 0, 903), /* processBodyStmtNodesTwoPass return */ - reg::packed(357, 0, 375), /* inferStaticVariableTypes $nodeScopeResolver */ - reg::packed(588, 0, 599), /* inferStaticVariableTypes $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* inferStaticVariableTypes $stmts */ - reg::packed(1398, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticSites */ - reg::packed(1410, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticStatementIndexes */ - reg::packed(1433, MAY_BE_NULL, 1445), /* inferStaticVariableTypes $parentFrame */ - reg::packed(1493, MAY_BE_ARRAY), /* inferStaticVariableTypes $entries */ - reg::packed(1501, 0, 1511), /* inferStaticVariableTypes $recording */ - reg::packed(624, 0, 477), /* inferStaticVariableTypes $storage */ - reg::packed(632, 0, 640), /* inferStaticVariableTypes $context */ - reg::packed(531, MAY_BE_ARRAY), /* inferStaticVariableTypes return */ - reg::packed(357, 0, 375), /* replayStaticVariableWalk $nodeScopeResolver */ - reg::packed(897, MAY_BE_ARRAY), /* replayStaticVariableWalk $stmts */ - reg::packed(1493, MAY_BE_ARRAY), /* replayStaticVariableWalk $entries */ - reg::packed(1501, 0, 1511), /* replayStaticVariableWalk $recording */ - reg::packed(1575, MAY_BE_ARRAY), /* replayStaticVariableWalk $staticWalk */ - reg::packed(1586, 0, 1445), /* replayStaticVariableWalk $frame */ - reg::packed(1433, MAY_BE_NULL, 1445), /* replayStaticVariableWalk $parentFrame */ - reg::packed(1592, MAY_BE_NULL, 1610), /* replayStaticVariableWalk $parentConstraints */ - reg::packed(624, 0, 477), /* replayStaticVariableWalk $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* replayStaticVariableWalk $nodeCallback */ - reg::packed(416, 0, 422), /* replayStaticVariableWalk $scope */ - reg::packed(531, 0, 903), /* replayStaticVariableWalk return */ - reg::packed(1689, 0, 422), /* collectStaticVariableScopes $bodyScope */ - reg::packed(1501, 0, 1511), /* collectStaticVariableScopes $recording */ - reg::packed(1699, 0, 1130), /* collectStaticVariableScopes $endState */ - reg::packed(1708, MAY_BE_ARRAY), /* collectStaticVariableScopes $moreScopes */ - reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableScopes return */ - reg::packed(1689, 0, 422), /* collectStaticVariableStateScopes $bodyScope */ - reg::packed(1501, 0, 1511), /* collectStaticVariableStateScopes $recording */ - reg::packed(1699, 0, 1130), /* collectStaticVariableStateScopes $endState */ - reg::packed(1708, MAY_BE_ARRAY), /* collectStaticVariableStateScopes $moreScopes */ - reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableStateScopes return */ - reg::packed(1780, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ - reg::packed(1786, MAY_BE_ARRAY), /* collectStaticVariableTypes $scopes */ - reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableTypes return */ - reg::packed(1820, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $runs */ - reg::packed(1786, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $scopes */ - reg::packed(624, 0, 477), /* collectStaticVariableConditionalExpressions $storage */ - reg::packed(1825, MAY_BE_NULL | MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $types */ - reg::packed(1831, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $walkedConditionalExpressions */ - reg::packed(1860, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $enteredTypes */ - reg::packed(531, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions return */ - reg::packed(1124, MAY_BE_ARRAY), /* isWalkedStaticVariableState $state */ - reg::packed(1825, MAY_BE_ARRAY), /* isWalkedStaticVariableState $types */ - reg::packed(416, 0, 422), /* isWalkedStaticVariableState $scope */ - reg::packed(1831, MAY_BE_ARRAY), /* isWalkedStaticVariableState $walkedConditionalExpressions */ - reg::packed(531, MAY_BE_BOOL), /* isWalkedStaticVariableState return */ - reg::packed(1945, MAY_BE_ARRAY), /* equalStaticVariableStates $a */ - reg::packed(1947, MAY_BE_ARRAY), /* equalStaticVariableStates $b */ - reg::packed(531, MAY_BE_BOOL), /* equalStaticVariableStates return */ - reg::packed(1945, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $a */ - reg::packed(1947, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $b */ - reg::packed(531, MAY_BE_BOOL), /* equalStaticVariableConditionalExpressions return */ - reg::packed(1945, 0, 2017), /* equalConditionalExpressionHolders $a */ - reg::packed(1947, 0, 2017), /* equalConditionalExpressionHolders $b */ - reg::packed(531, MAY_BE_BOOL), /* equalConditionalExpressionHolders return */ - reg::packed(1689, 0, 422), /* joinStaticVariableTypes $bodyScope */ - reg::packed(1780, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ - reg::packed(1825, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ - reg::packed(2096, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ - reg::packed(2106, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ - reg::packed(531, MAY_BE_ARRAY), /* joinStaticVariableTypes return */ - reg::packed(531, 0, 2141), /* getClosureProcessor return */ - reg::packed(357, 0, 375), /* processDeferredByRefClosureBodies $nodeScopeResolver */ - reg::packed(1586, 0, 1445), /* processDeferredByRefClosureBodies $frame */ - reg::packed(1124, 0, 1130), /* processDeferredByRefClosureBodies $state */ - reg::packed(624, 0, 477), /* processDeferredByRefClosureBodies $storage */ - reg::packed(518, MAY_BE_CALLABLE), /* processDeferredByRefClosureBodies $nodeCallback */ - reg::packed(531, MAY_BE_VOID), /* processDeferredByRefClosureBodies return */ - reg::packed(357, 0, 375), /* observeClosureSignatures $nodeScopeResolver */ - reg::packed(588, 0, 599), /* observeClosureSignatures $parentNode */ - reg::packed(897, MAY_BE_ARRAY), /* observeClosureSignatures $stmts */ - reg::packed(1586, 0, 1445), /* observeClosureSignatures $frame */ - reg::packed(1493, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ - reg::packed(624, 0, 477), /* observeClosureSignatures $storage */ - reg::packed(632, 0, 640), /* observeClosureSignatures $context */ - reg::packed(2229, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ - reg::packed(531, 0, 1445), /* observeClosureSignatures return */ - reg::packed(416, 0, 422), /* withRecordedConstraints $scope */ - reg::packed(1201, 0, 422), /* withRecordedConstraints $recordedEntry */ - reg::packed(2283, 0, 422), /* withRecordedConstraints $recordedExit */ - reg::packed(531, 0, 422), /* withRecordedConstraints return */ - reg::packed(897, MAY_BE_ARRAY), /* containsLabels $stmts */ - reg::packed(531, MAY_BE_BOOL), /* containsLabels return */ - reg::packed(1124, 0, 1130), /* appendRecordedStatementResults $state */ - reg::packed(2335, 0, 1130), /* appendRecordedStatementResults $from */ - reg::packed(2340, 0, 1130), /* appendRecordedStatementResults $to */ - reg::packed(531, MAY_BE_VOID), /* appendRecordedStatementResults return */ - reg::packed(1238, 0, 1243), /* getVariableMentionFlow $stmt */ - reg::packed(531, MAY_BE_NULL, 2374), /* getVariableMentionFlow return */ - reg::packed(1238, 0, 1243), /* statementMentionsAnyVariable $stmt */ - reg::packed(1086, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ - reg::packed(531, MAY_BE_BOOL), /* statementMentionsAnyVariable return */ - reg::packed(2456, 0, 599), /* collectMentionedVariables $node */ - reg::packed(1780, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ - reg::packed(2461, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ - reg::packed(531, MAY_BE_VOID), /* collectMentionedVariables return */ - reg::packed(1238, 0, 1243), /* getVarTagDependencies $stmt */ - reg::packed(416, 0, 422), /* getVarTagDependencies $scope */ - reg::packed(531, MAY_BE_NULL, 532), /* getVarTagDependencies return */ - reg::packed(2528, 0, 1243), /* getOverridingThrowPoints $statement */ - reg::packed(416, 0, 422), /* getOverridingThrowPoints $scope */ - reg::packed(531, MAY_BE_NULL | MAY_BE_ARRAY), /* getOverridingThrowPoints return */ - reg::packed(357, 0, 375), /* processStmtVarAnnotation $nodeScopeResolver */ - reg::packed(416, 0, 422), /* processStmtVarAnnotation $scope */ - reg::packed(624, 0, 477), /* processStmtVarAnnotation $storage */ - reg::packed(1238, 0, 1243), /* processStmtVarAnnotation $stmt */ - reg::packed(2563, MAY_BE_NULL, 2575), /* processStmtVarAnnotation $defaultExpr */ - reg::packed(518, MAY_BE_CALLABLE), /* processStmtVarAnnotation $nodeCallback */ - reg::packed(531, 0, 422), /* processStmtVarAnnotation return */ - reg::packed(416, 0, 422), /* findSingleVariableLessVarTag $scope */ - reg::packed(1238, 0, 1243), /* findSingleVariableLessVarTag $stmt */ - reg::packed(531, MAY_BE_NULL, 2620), /* findSingleVariableLessVarTag return */ - reg::packed(357, 0, 375), /* emitVarTagChangedNode $nodeScopeResolver */ - reg::packed(416, 0, 422), /* emitVarTagChangedNode $scope */ - reg::packed(624, 0, 477), /* emitVarTagChangedNode $storage */ - reg::packed(1238, 0, 1243), /* emitVarTagChangedNode $stmt */ - reg::packed(2563, 0, 2575), /* emitVarTagChangedNode $defaultExpr */ - reg::packed(518, MAY_BE_CALLABLE), /* emitVarTagChangedNode $nodeCallback */ - reg::packed(531, 0, 1610), /* emitVarTagChangedNode return */ + reg::packed(317, 0, 341), /* __construct $redundantVarTagDetector */ + reg::packed(382, MAY_BE_BOOL), /* __construct $unresolvedTemplateArguments */ + reg::packed(410, MAY_BE_BOOL), /* __construct $reportRedundantVarTag */ + reg::packed(444, 0, 462), /* processNodesWithStorage $nodeScopeResolver */ + reg::packed(497, MAY_BE_ARRAY), /* processNodesWithStorage $nodes */ + reg::packed(503, 0, 509), /* processNodesWithStorage $scope */ + reg::packed(540, 0, 564), /* processNodesWithStorage $expressionResultStorage */ + reg::packed(605, MAY_BE_CALLABLE), /* processNodesWithStorage $nodeCallback */ + reg::packed(618, MAY_BE_NULL, 619), /* processNodesWithStorage return */ + reg::packed(444, 0, 462), /* resolveBackwardGotoScope $nodeScopeResolver */ + reg::packed(675, 0, 686), /* resolveBackwardGotoScope $parentNode */ + reg::packed(701, MAY_BE_ARRAY), /* resolveBackwardGotoScope $bodyStmts */ + reg::packed(503, 0, 509), /* resolveBackwardGotoScope $scope */ + reg::packed(711, 0, 564), /* resolveBackwardGotoScope $storage */ + reg::packed(719, 0, 727), /* resolveBackwardGotoScope $context */ + reg::packed(761, 0, 777), /* resolveBackwardGotoScope $gotoNameMatcher */ + reg::packed(785, MAY_BE_BOOL), /* resolveBackwardGotoScope $mergeBodyScopeEachIteration */ + reg::packed(618, 0, 509), /* resolveBackwardGotoScope return */ + reg::packed(838, MAY_BE_STRING), /* mergeForwardGotoExitPoints $labelName */ + reg::packed(503, 0, 509), /* mergeForwardGotoExitPoints $scope */ + reg::packed(848, MAY_BE_BOOL), /* mergeForwardGotoExitPoints $alreadyTerminated */ + reg::packed(866, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints $exitPoints */ + reg::packed(618, MAY_BE_ARRAY), /* mergeForwardGotoExitPoints return */ + reg::packed(444, 0, 462), /* processUnreachableStatement $nodeScopeResolver */ + reg::packed(904, MAY_BE_ARRAY), /* processUnreachableStatement $nextStmts */ + reg::packed(503, 0, 509), /* processUnreachableStatement $scope */ + reg::packed(711, 0, 564), /* processUnreachableStatement $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* processUnreachableStatement $nodeCallback */ + reg::packed(618, MAY_BE_VOID), /* processUnreachableStatement return */ + reg::packed(497, MAY_BE_ARRAY), /* getNextUnreachableStatements $nodes */ + reg::packed(942, MAY_BE_BOOL), /* getNextUnreachableStatements $earlyBinding */ + reg::packed(618, MAY_BE_ARRAY), /* getNextUnreachableStatements return */ + reg::packed(444, 0, 462), /* doProcessStmtNodes $nodeScopeResolver */ + reg::packed(675, 0, 686), /* doProcessStmtNodes $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* doProcessStmtNodes $stmts */ + reg::packed(503, 0, 509), /* doProcessStmtNodes $scope */ + reg::packed(711, 0, 564), /* doProcessStmtNodes $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* doProcessStmtNodes $nodeCallback */ + reg::packed(719, 0, 727), /* doProcessStmtNodes $context */ + reg::packed(618, 0, 990), /* doProcessStmtNodes return */ + reg::packed(444, 0, 462), /* processLoopPassStmtNodes $nodeScopeResolver */ + reg::packed(675, 0, 686), /* processLoopPassStmtNodes $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* processLoopPassStmtNodes $stmts */ + reg::packed(503, 0, 509), /* processLoopPassStmtNodes $scope */ + reg::packed(711, 0, 564), /* processLoopPassStmtNodes $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* processLoopPassStmtNodes $nodeCallback */ + reg::packed(719, 0, 727), /* processLoopPassStmtNodes $context */ + reg::packed(1050, MAY_BE_NULL | MAY_BE_ARRAY), /* processLoopPassStmtNodes $previous */ + reg::packed(1059, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, true, false), /* processLoopPassStmtNodes $record */ + reg::packed(618, 0, 990), /* processLoopPassStmtNodes return */ + reg::packed(444, 0, 462), /* processLoopFinalStmtNodes $nodeScopeResolver */ + reg::packed(675, 0, 686), /* processLoopFinalStmtNodes $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $stmts */ + reg::packed(503, 0, 509), /* processLoopFinalStmtNodes $scope */ + reg::packed(711, 0, 564), /* processLoopFinalStmtNodes $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* processLoopFinalStmtNodes $nodeCallback */ + reg::packed(719, 0, 727), /* processLoopFinalStmtNodes $context */ + reg::packed(1091, MAY_BE_ARRAY), /* processLoopFinalStmtNodes $lastPass */ + reg::packed(618, 0, 990), /* processLoopFinalStmtNodes return */ + reg::packed(444, 0, 462), /* walkAgainstPreviousPass $nodeScopeResolver */ + reg::packed(675, 0, 686), /* walkAgainstPreviousPass $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* walkAgainstPreviousPass $stmts */ + reg::packed(503, 0, 509), /* walkAgainstPreviousPass $scope */ + reg::packed(711, 0, 564), /* walkAgainstPreviousPass $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* walkAgainstPreviousPass $nodeCallback */ + reg::packed(719, 0, 727), /* walkAgainstPreviousPass $context */ + reg::packed(1050, MAY_BE_NULL | MAY_BE_ARRAY), /* walkAgainstPreviousPass $previous */ + reg::packed(1126, MAY_BE_NULL | MAY_BE_CALLABLE), /* walkAgainstPreviousPass $replayTarget */ + reg::packed(1139, MAY_BE_BOOL), /* walkAgainstPreviousPass $finalWalk */ + reg::packed(618, MAY_BE_ARRAY), /* walkAgainstPreviousPass return */ + reg::packed(503, 0, 509), /* withByRefClosureHolders $scope */ + reg::packed(1173, MAY_BE_ARRAY), /* withByRefClosureHolders $variableNames */ + reg::packed(618, MAY_BE_ARRAY), /* withByRefClosureHolders return */ + reg::packed(1211, 0, 1217), /* rebaseAppendedPoints $state */ + reg::packed(1257, MAY_BE_LONG), /* rebaseAppendedPoints $exitPointCount */ + reg::packed(1272, MAY_BE_LONG), /* rebaseAppendedPoints $throwPointCount */ + reg::packed(1288, 0, 509), /* rebaseAppendedPoints $recordedEntry */ + reg::packed(618, MAY_BE_VOID), /* rebaseAppendedPoints return */ + reg::packed(444, 0, 462), /* processStatementStep $nodeScopeResolver */ + reg::packed(675, 0, 686), /* processStatementStep $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* processStatementStep $stmts */ + reg::packed(1323, MAY_BE_LONG), /* processStatementStep $i */ + reg::packed(1325, 0, 1330), /* processStatementStep $stmt */ + reg::packed(1211, 0, 1217), /* processStatementStep $state */ + reg::packed(711, 0, 564), /* processStatementStep $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* processStatementStep $nodeCallback */ + reg::packed(719, 0, 727), /* processStatementStep $context */ + reg::packed(1350, MAY_BE_BOOL), /* processStatementStep $shouldCheckLastStatement */ + reg::packed(618, MAY_BE_VOID), /* processStatementStep return */ + reg::packed(1325, 0, 1330), /* readEndStatementExprResult $stmt */ + reg::packed(711, 0, 564), /* readEndStatementExprResult $storage */ + reg::packed(618, MAY_BE_NULL, 1396), /* readEndStatementExprResult return */ + reg::packed(444, 0, 462), /* processBodyStmtNodesTwoPass $nodeScopeResolver */ + reg::packed(675, 0, 686), /* processBodyStmtNodesTwoPass $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* processBodyStmtNodesTwoPass $stmts */ + reg::packed(503, 0, 509), /* processBodyStmtNodesTwoPass $scope */ + reg::packed(711, 0, 564), /* processBodyStmtNodesTwoPass $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* processBodyStmtNodesTwoPass $nodeCallback */ + reg::packed(719, 0, 727), /* processBodyStmtNodesTwoPass $context */ + reg::packed(618, 0, 990), /* processBodyStmtNodesTwoPass return */ + reg::packed(444, 0, 462), /* inferStaticVariableTypes $nodeScopeResolver */ + reg::packed(675, 0, 686), /* inferStaticVariableTypes $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* inferStaticVariableTypes $stmts */ + reg::packed(1485, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticSites */ + reg::packed(1497, MAY_BE_ARRAY), /* inferStaticVariableTypes $staticStatementIndexes */ + reg::packed(1520, MAY_BE_NULL, 1532), /* inferStaticVariableTypes $parentFrame */ + reg::packed(1580, MAY_BE_ARRAY), /* inferStaticVariableTypes $entries */ + reg::packed(1588, 0, 1598), /* inferStaticVariableTypes $recording */ + reg::packed(711, 0, 564), /* inferStaticVariableTypes $storage */ + reg::packed(719, 0, 727), /* inferStaticVariableTypes $context */ + reg::packed(618, MAY_BE_ARRAY), /* inferStaticVariableTypes return */ + reg::packed(444, 0, 462), /* replayStaticVariableWalk $nodeScopeResolver */ + reg::packed(984, MAY_BE_ARRAY), /* replayStaticVariableWalk $stmts */ + reg::packed(1580, MAY_BE_ARRAY), /* replayStaticVariableWalk $entries */ + reg::packed(1588, 0, 1598), /* replayStaticVariableWalk $recording */ + reg::packed(1662, MAY_BE_ARRAY), /* replayStaticVariableWalk $staticWalk */ + reg::packed(1673, 0, 1532), /* replayStaticVariableWalk $frame */ + reg::packed(1520, MAY_BE_NULL, 1532), /* replayStaticVariableWalk $parentFrame */ + reg::packed(1679, MAY_BE_NULL, 1697), /* replayStaticVariableWalk $parentConstraints */ + reg::packed(711, 0, 564), /* replayStaticVariableWalk $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* replayStaticVariableWalk $nodeCallback */ + reg::packed(503, 0, 509), /* replayStaticVariableWalk $scope */ + reg::packed(618, 0, 990), /* replayStaticVariableWalk return */ + reg::packed(1776, 0, 509), /* collectStaticVariableScopes $bodyScope */ + reg::packed(1588, 0, 1598), /* collectStaticVariableScopes $recording */ + reg::packed(1786, 0, 1217), /* collectStaticVariableScopes $endState */ + reg::packed(1795, MAY_BE_ARRAY), /* collectStaticVariableScopes $moreScopes */ + reg::packed(618, MAY_BE_ARRAY), /* collectStaticVariableScopes return */ + reg::packed(1776, 0, 509), /* collectStaticVariableStateScopes $bodyScope */ + reg::packed(1588, 0, 1598), /* collectStaticVariableStateScopes $recording */ + reg::packed(1786, 0, 1217), /* collectStaticVariableStateScopes $endState */ + reg::packed(1795, MAY_BE_ARRAY), /* collectStaticVariableStateScopes $moreScopes */ + reg::packed(618, MAY_BE_ARRAY), /* collectStaticVariableStateScopes return */ + reg::packed(1867, MAY_BE_ARRAY), /* collectStaticVariableTypes $names */ + reg::packed(1873, MAY_BE_ARRAY), /* collectStaticVariableTypes $scopes */ + reg::packed(618, MAY_BE_ARRAY), /* collectStaticVariableTypes return */ + reg::packed(1907, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $runs */ + reg::packed(1873, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $scopes */ + reg::packed(711, 0, 564), /* collectStaticVariableConditionalExpressions $storage */ + reg::packed(1912, MAY_BE_NULL | MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $types */ + reg::packed(1918, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $walkedConditionalExpressions */ + reg::packed(1947, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions $enteredTypes */ + reg::packed(618, MAY_BE_ARRAY), /* collectStaticVariableConditionalExpressions return */ + reg::packed(1211, MAY_BE_ARRAY), /* isWalkedStaticVariableState $state */ + reg::packed(1912, MAY_BE_ARRAY), /* isWalkedStaticVariableState $types */ + reg::packed(503, 0, 509), /* isWalkedStaticVariableState $scope */ + reg::packed(1918, MAY_BE_ARRAY), /* isWalkedStaticVariableState $walkedConditionalExpressions */ + reg::packed(618, MAY_BE_BOOL), /* isWalkedStaticVariableState return */ + reg::packed(2032, MAY_BE_ARRAY), /* equalStaticVariableStates $a */ + reg::packed(2034, MAY_BE_ARRAY), /* equalStaticVariableStates $b */ + reg::packed(618, MAY_BE_BOOL), /* equalStaticVariableStates return */ + reg::packed(2032, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $a */ + reg::packed(2034, MAY_BE_ARRAY), /* equalStaticVariableConditionalExpressions $b */ + reg::packed(618, MAY_BE_BOOL), /* equalStaticVariableConditionalExpressions return */ + reg::packed(2032, 0, 2104), /* equalConditionalExpressionHolders $a */ + reg::packed(2034, 0, 2104), /* equalConditionalExpressionHolders $b */ + reg::packed(618, MAY_BE_BOOL), /* equalConditionalExpressionHolders return */ + reg::packed(1776, 0, 509), /* joinStaticVariableTypes $bodyScope */ + reg::packed(1867, MAY_BE_ARRAY), /* joinStaticVariableTypes $names */ + reg::packed(1912, MAY_BE_ARRAY), /* joinStaticVariableTypes $types */ + reg::packed(2183, MAY_BE_ARRAY), /* joinStaticVariableTypes $walkTypes */ + reg::packed(2193, MAY_BE_BOOL), /* joinStaticVariableTypes $generalize */ + reg::packed(618, MAY_BE_ARRAY), /* joinStaticVariableTypes return */ + reg::packed(618, 0, 2228), /* getClosureProcessor return */ + reg::packed(444, 0, 462), /* processDeferredByRefClosureBodies $nodeScopeResolver */ + reg::packed(1673, 0, 1532), /* processDeferredByRefClosureBodies $frame */ + reg::packed(1211, 0, 1217), /* processDeferredByRefClosureBodies $state */ + reg::packed(711, 0, 564), /* processDeferredByRefClosureBodies $storage */ + reg::packed(605, MAY_BE_CALLABLE), /* processDeferredByRefClosureBodies $nodeCallback */ + reg::packed(618, MAY_BE_VOID), /* processDeferredByRefClosureBodies return */ + reg::packed(444, 0, 462), /* observeClosureSignatures $nodeScopeResolver */ + reg::packed(675, 0, 686), /* observeClosureSignatures $parentNode */ + reg::packed(984, MAY_BE_ARRAY), /* observeClosureSignatures $stmts */ + reg::packed(1673, 0, 1532), /* observeClosureSignatures $frame */ + reg::packed(1580, MAY_BE_ARRAY), /* observeClosureSignatures $entries */ + reg::packed(711, 0, 564), /* observeClosureSignatures $storage */ + reg::packed(719, 0, 727), /* observeClosureSignatures $context */ + reg::packed(2316, MAY_BE_ARRAY), /* observeClosureSignatures $statementStartTokenPositions */ + reg::packed(618, 0, 1532), /* observeClosureSignatures return */ + reg::packed(503, 0, 509), /* withRecordedConstraints $scope */ + reg::packed(1288, 0, 509), /* withRecordedConstraints $recordedEntry */ + reg::packed(2370, 0, 509), /* withRecordedConstraints $recordedExit */ + reg::packed(618, 0, 509), /* withRecordedConstraints return */ + reg::packed(984, MAY_BE_ARRAY), /* containsLabels $stmts */ + reg::packed(618, MAY_BE_BOOL), /* containsLabels return */ + reg::packed(1211, 0, 1217), /* appendRecordedStatementResults $state */ + reg::packed(2422, 0, 1217), /* appendRecordedStatementResults $from */ + reg::packed(2427, 0, 1217), /* appendRecordedStatementResults $to */ + reg::packed(618, MAY_BE_VOID), /* appendRecordedStatementResults return */ + reg::packed(1325, 0, 1330), /* getVariableMentionFlow $stmt */ + reg::packed(618, MAY_BE_NULL, 2461), /* getVariableMentionFlow return */ + reg::packed(1325, 0, 1330), /* statementMentionsAnyVariable $stmt */ + reg::packed(1173, MAY_BE_ARRAY), /* statementMentionsAnyVariable $variableNames */ + reg::packed(618, MAY_BE_BOOL), /* statementMentionsAnyVariable return */ + reg::packed(2543, 0, 686), /* collectMentionedVariables $node */ + reg::packed(1867, MAY_BE_ARRAY, reg::NoString, true, false), /* collectMentionedVariables $names */ + reg::packed(2548, MAY_BE_BOOL, reg::NoString, true, false), /* collectMentionedVariables $mentionsEverything */ + reg::packed(618, MAY_BE_VOID), /* collectMentionedVariables return */ + reg::packed(1325, 0, 1330), /* getVarTagDependencies $stmt */ + reg::packed(503, 0, 509), /* getVarTagDependencies $scope */ + reg::packed(618, MAY_BE_NULL, 619), /* getVarTagDependencies return */ + reg::packed(2615, 0, 1330), /* getOverridingThrowPoints $statement */ + reg::packed(503, 0, 509), /* getOverridingThrowPoints $scope */ + reg::packed(618, MAY_BE_NULL | MAY_BE_ARRAY), /* getOverridingThrowPoints return */ + reg::packed(444, 0, 462), /* processStmtVarAnnotation $nodeScopeResolver */ + reg::packed(503, 0, 509), /* processStmtVarAnnotation $scope */ + reg::packed(711, 0, 564), /* processStmtVarAnnotation $storage */ + reg::packed(1325, 0, 1330), /* processStmtVarAnnotation $stmt */ + reg::packed(2650, MAY_BE_NULL, 2662), /* processStmtVarAnnotation $defaultExpr */ + reg::packed(605, MAY_BE_CALLABLE), /* processStmtVarAnnotation $nodeCallback */ + reg::packed(618, 0, 509), /* processStmtVarAnnotation return */ + reg::packed(503, 0, 509), /* findSingleVariableLessVarTag $scope */ + reg::packed(1325, 0, 1330), /* findSingleVariableLessVarTag $stmt */ + reg::packed(618, MAY_BE_NULL, 2707), /* findSingleVariableLessVarTag return */ + reg::packed(444, 0, 462), /* emitVarTagChangedNode $nodeScopeResolver */ + reg::packed(503, 0, 509), /* emitVarTagChangedNode $scope */ + reg::packed(711, 0, 564), /* emitVarTagChangedNode $storage */ + reg::packed(1325, 0, 1330), /* emitVarTagChangedNode $stmt */ + reg::packed(2650, 0, 2662), /* emitVarTagChangedNode $defaultExpr */ + reg::packed(605, MAY_BE_CALLABLE), /* emitVarTagChangedNode $nodeCallback */ + reg::packed(618, 0, 1697), /* emitVarTagChangedNode return */ + reg::packed(444, 0, 462), /* emitRedundantVarTagNodes $nodeScopeResolver */ + reg::packed(503, 0, 509), /* emitRedundantVarTagNodes $scope */ + reg::packed(711, 0, 564), /* emitRedundantVarTagNodes $storage */ + reg::packed(1325, 0, 1330), /* emitRedundantVarTagNodes $stmt */ + reg::packed(605, MAY_BE_CALLABLE), /* emitRedundantVarTagNodes $nodeCallback */ + reg::packed(618, MAY_BE_VOID), /* emitRedundantVarTagNodes 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 = { { 345 /* __construct */, 6, 0, 6, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processNodesWithStorage = { { 564 /* processNodesWithStorage */, 5, 6, 5, 11, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveBackwardGotoScope = { { 726 /* resolveBackwardGotoScope */, 8, 12, 8, 20, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mergeForwardGotoExitPoints = { { 790 /* mergeForwardGotoExitPoints */, 4, 21, 4, 25, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processUnreachableStatement = { { 827 /* processUnreachableStatement */, 5, 26, 5, 31, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getNextUnreachableStatements = { { 868 /* getNextUnreachableStatements */, 2, 32, 2, 34, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig doProcessStmtNodes = { { 944 /* doProcessStmtNodes */, 7, 35, 7, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processLoopPassStmtNodes = { { 979 /* processLoopPassStmtNodes */, 9, 43, 9, 52, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processLoopFinalStmtNodes = { { 1013 /* processLoopFinalStmtNodes */, 8, 53, 8, 61, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig walkAgainstPreviousPass = { { 1062 /* walkAgainstPreviousPass */, 10, 62, 10, 72, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig withByRefClosureHolders = { { 1100 /* withByRefClosureHolders */, 2, 73, 2, 75, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig rebaseAppendedPoints = { { 1215 /* rebaseAppendedPoints */, 4, 76, 4, 80, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processStatementStep = { { 1288 /* processStatementStep */, 10, 81, 10, 91, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig readEndStatementExprResult = { { 1343 /* readEndStatementExprResult */, 2, 92, 2, 94, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1370 /* processBodyStmtNodesTwoPass */, 7, 95, 7, 102, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig inferStaticVariableTypes = { { 1550 /* inferStaticVariableTypes */, 10, 103, 10, 113, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig replayStaticVariableWalk = { { 1664 /* replayStaticVariableWalk */, 11, 114, 11, 125, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableScopes = { { 1719 /* collectStaticVariableScopes */, 4, 126, 4, 130, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableStateScopes = { { 1747 /* collectStaticVariableStateScopes */, 4, 131, 4, 135, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1793 /* collectStaticVariableTypes */, 2, 136, 2, 138, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectStaticVariableConditionalExpressions = { { 1873 /* collectStaticVariableConditionalExpressions */, 6, 139, 6, 145, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isWalkedStaticVariableState = { { 1917 /* isWalkedStaticVariableState */, 4, 146, 4, 150, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalStaticVariableStates = { { 1949 /* equalStaticVariableStates */, 2, 151, 2, 153, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalStaticVariableConditionalExpressions = { { 1975 /* equalStaticVariableConditionalExpressions */, 2, 154, 2, 156, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equalConditionalExpressionHolders = { { 2062 /* equalConditionalExpressionHolders */, 2, 157, 2, 159, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig joinStaticVariableTypes = { { 2117 /* joinStaticVariableTypes */, 5, 160, 5, 165, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getClosureProcessor = { { 2175 /* getClosureProcessor */, 0, 166, 0, 166, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 2195 /* processDeferredByRefClosureBodies */, 5, 167, 5, 172, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig observeClosureSignatures = { { 2258 /* observeClosureSignatures */, 8, 173, 8, 181, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig withRecordedConstraints = { { 2296 /* withRecordedConstraints */, 3, 182, 3, 185, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig containsLabels = { { 2320 /* containsLabels */, 1, 186, 1, 187, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig appendRecordedStatementResults = { { 2343 /* appendRecordedStatementResults */, 3, 188, 3, 191, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getVariableMentionFlow = { { 2404 /* getVariableMentionFlow */, 1, 192, 1, 193, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 2427 /* statementMentionsAnyVariable */, 2, 194, 2, 196, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectMentionedVariables = { { 2480 /* collectMentionedVariables */, 3, 197, 3, 200, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getVarTagDependencies = { { 2506 /* getVarTagDependencies */, 2, 201, 2, 203, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getOverridingThrowPoints = { { 2538 /* getOverridingThrowPoints */, 2, 204, 2, 206, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processStmtVarAnnotation = { { 2595 /* processStmtVarAnnotation */, 6, 207, 6, 213, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 2646 /* findSingleVariableLessVarTag */, 2, 214, 2, 216, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2675 /* emitVarTagChangedNode */, 6, 217, 6, 223, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig __construct = { { 432 /* __construct */, 8, 0, 8, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processNodesWithStorage = { { 651 /* processNodesWithStorage */, 5, 8, 5, 13, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveBackwardGotoScope = { { 813 /* resolveBackwardGotoScope */, 8, 14, 8, 22, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeForwardGotoExitPoints = { { 877 /* mergeForwardGotoExitPoints */, 4, 23, 4, 27, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processUnreachableStatement = { { 914 /* processUnreachableStatement */, 5, 28, 5, 33, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getNextUnreachableStatements = { { 955 /* getNextUnreachableStatements */, 2, 34, 2, 36, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig doProcessStmtNodes = { { 1031 /* doProcessStmtNodes */, 7, 37, 7, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processLoopPassStmtNodes = { { 1066 /* processLoopPassStmtNodes */, 9, 45, 9, 54, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processLoopFinalStmtNodes = { { 1100 /* processLoopFinalStmtNodes */, 8, 55, 8, 63, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig walkAgainstPreviousPass = { { 1149 /* walkAgainstPreviousPass */, 10, 64, 10, 74, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withByRefClosureHolders = { { 1187 /* withByRefClosureHolders */, 2, 75, 2, 77, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig rebaseAppendedPoints = { { 1302 /* rebaseAppendedPoints */, 4, 78, 4, 82, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processStatementStep = { { 1375 /* processStatementStep */, 10, 83, 10, 93, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig readEndStatementExprResult = { { 1430 /* readEndStatementExprResult */, 2, 94, 2, 96, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processBodyStmtNodesTwoPass = { { 1457 /* processBodyStmtNodesTwoPass */, 7, 97, 7, 104, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig inferStaticVariableTypes = { { 1637 /* inferStaticVariableTypes */, 10, 105, 10, 115, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig replayStaticVariableWalk = { { 1751 /* replayStaticVariableWalk */, 11, 116, 11, 127, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableScopes = { { 1806 /* collectStaticVariableScopes */, 4, 128, 4, 132, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableStateScopes = { { 1834 /* collectStaticVariableStateScopes */, 4, 133, 4, 137, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableTypes = { { 1880 /* collectStaticVariableTypes */, 2, 138, 2, 140, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectStaticVariableConditionalExpressions = { { 1960 /* collectStaticVariableConditionalExpressions */, 6, 141, 6, 147, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isWalkedStaticVariableState = { { 2004 /* isWalkedStaticVariableState */, 4, 148, 4, 152, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalStaticVariableStates = { { 2036 /* equalStaticVariableStates */, 2, 153, 2, 155, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalStaticVariableConditionalExpressions = { { 2062 /* equalStaticVariableConditionalExpressions */, 2, 156, 2, 158, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equalConditionalExpressionHolders = { { 2149 /* equalConditionalExpressionHolders */, 2, 159, 2, 161, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig joinStaticVariableTypes = { { 2204 /* joinStaticVariableTypes */, 5, 162, 5, 167, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClosureProcessor = { { 2262 /* getClosureProcessor */, 0, 168, 0, 168, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processDeferredByRefClosureBodies = { { 2282 /* processDeferredByRefClosureBodies */, 5, 169, 5, 174, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig observeClosureSignatures = { { 2345 /* observeClosureSignatures */, 8, 175, 8, 183, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withRecordedConstraints = { { 2383 /* withRecordedConstraints */, 3, 184, 3, 187, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig containsLabels = { { 2407 /* containsLabels */, 1, 188, 1, 189, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig appendRecordedStatementResults = { { 2430 /* appendRecordedStatementResults */, 3, 190, 3, 193, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getVariableMentionFlow = { { 2491 /* getVariableMentionFlow */, 1, 194, 1, 195, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig statementMentionsAnyVariable = { { 2514 /* statementMentionsAnyVariable */, 2, 196, 2, 198, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectMentionedVariables = { { 2567 /* collectMentionedVariables */, 3, 199, 3, 202, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getVarTagDependencies = { { 2593 /* getVarTagDependencies */, 2, 203, 2, 205, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getOverridingThrowPoints = { { 2625 /* getOverridingThrowPoints */, 2, 206, 2, 208, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processStmtVarAnnotation = { { 2682 /* processStmtVarAnnotation */, 6, 209, 6, 215, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findSingleVariableLessVarTag = { { 2733 /* findSingleVariableLessVarTag */, 2, 216, 2, 218, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig emitVarTagChangedNode = { { 2762 /* emitVarTagChangedNode */, 6, 219, 6, 225, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig emitRedundantVarTagNodes = { { 2784 /* emitRedundantVarTagNodes */, 5, 226, 5, 231, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::StatementsHandler diff --git a/turbo-ext/src/generated/VarAnnotationProcessor.h b/turbo-ext/src/generated/VarAnnotationProcessor.h index 105f87ccd32..ef1b797af0d 100644 --- a/turbo-ext/src/generated/VarAnnotationProcessor.h +++ b/turbo-ext/src/generated/VarAnnotationProcessor.h @@ -11,6 +11,8 @@ namespace ptdecl::VarAnnotationProcessor { /* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ namespace slot { inline constexpr uint32_t fileTypeMapper = 0; +inline constexpr uint32_t redundantVarTagDetector = 1; +inline constexpr uint32_t reportRedundantVarTag = 2; } // namespace slot inline void declareClass(reg::Class &cls) @@ -22,6 +24,8 @@ inline void declareClass(reg::Class &cls) inline void declareProperties(reg::Class &cls) { cls.property("fileTypeMapper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Type\\FileTypeMapper"); + cls.property("redundantVarTagDetector", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\RedundantVarTagDetector"); + cls.property("reportRedundantVarTag", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -29,31 +33,53 @@ namespace sigtab { inline constexpr char strings[] = "fileTypeMapper\0" /* 0 */ "PHPStan\\Type\\FileTypeMapper\0" /* 15 */ - "__construct\0" /* 43 */ - "scope\0" /* 55 */ - "PHPStan\\Analyser\\MutatingScope\0" /* 61 */ - "variableNames\0" /* 92 */ - "node\0" /* 106 */ - "PhpParser\\Node\\Stmt\0" /* 111 */ - "changed\0" /* 131 */ - "false\0" /* 139 */ - "\0" /* 145 */ - "processVarAnnotation"; /* 146 */ + "redundantVarTagDetector\0" /* 43 */ + "PHPStan\\Analyser\\RedundantVarTagDetector\0" /* 67 */ + "reportRedundantVarTag\0" /* 108 */ + "__construct\0" /* 130 */ + "scope\0" /* 142 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 148 */ + "variableNames\0" /* 179 */ + "node\0" /* 193 */ + "PhpParser\\Node\\Stmt\0" /* 198 */ + "changed\0" /* 218 */ + "false\0" /* 226 */ + "\0" /* 232 */ + "processVarAnnotation\0" /* 233 */ + "variableName\0" /* 254 */ + "PhpParser\\Node\\Stmt\\Foreach_\0" /* 267 */ + "processForeachIterateeVarAnnotation\0" /* 296 */ + "narrowedByForeach\0" /* 332 */ + "applyVarTags"; /* 350 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 15), /* __construct $fileTypeMapper */ - reg::packed(55, 0, 61), /* processVarAnnotation $scope */ - reg::packed(92, MAY_BE_ARRAY), /* processVarAnnotation $variableNames */ - reg::packed(106, 0, 111), /* processVarAnnotation $node */ - reg::packed(131, MAY_BE_BOOL, reg::NoString, true, false, 139), /* processVarAnnotation $changed */ - reg::packed(145, 0, 61), /* processVarAnnotation return */ + reg::packed(43, 0, 67), /* __construct $redundantVarTagDetector */ + reg::packed(108, MAY_BE_BOOL), /* __construct $reportRedundantVarTag */ + reg::packed(142, 0, 148), /* processVarAnnotation $scope */ + reg::packed(179, MAY_BE_ARRAY), /* processVarAnnotation $variableNames */ + reg::packed(193, 0, 198), /* processVarAnnotation $node */ + reg::packed(218, MAY_BE_BOOL, reg::NoString, true, false, 226), /* processVarAnnotation $changed */ + reg::packed(232, 0, 148), /* processVarAnnotation return */ + reg::packed(142, 0, 148), /* processForeachIterateeVarAnnotation $scope */ + reg::packed(254, MAY_BE_STRING), /* processForeachIterateeVarAnnotation $variableName */ + reg::packed(193, 0, 267), /* processForeachIterateeVarAnnotation $node */ + reg::packed(232, 0, 148), /* processForeachIterateeVarAnnotation return */ + reg::packed(142, 0, 148), /* applyVarTags $scope */ + reg::packed(179, MAY_BE_ARRAY), /* applyVarTags $variableNames */ + reg::packed(193, 0, 198), /* applyVarTags $node */ + reg::packed(218, MAY_BE_BOOL, reg::NoString, true, false), /* applyVarTags $changed */ + reg::packed(332, MAY_BE_BOOL), /* applyVarTags $narrowedByForeach */ + reg::packed(232, 0, 148), /* applyVarTags 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 = { { 43 /* __construct */, 1, 0, 1, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processVarAnnotation = { { 146 /* processVarAnnotation */, 3, 1, 4, 5, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig __construct = { { 130 /* __construct */, 3, 0, 3, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processVarAnnotation = { { 233 /* processVarAnnotation */, 3, 3, 4, 7, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processForeachIterateeVarAnnotation = { { 296 /* processForeachIterateeVarAnnotation */, 3, 8, 3, 11, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig applyVarTags = { { 350 /* applyVarTags */, 5, 12, 5, 17, ZEND_ACC_PRIVATE } }; } // namespace sig } // namespace ptdecl::VarAnnotationProcessor diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 6c0e26f9c3a..402aaadbd63 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -2380,6 +2380,9 @@ zv::Val pt_statements_handler_process_loop_final_stmt_nodes(zval *handler, zval zv::Val pt_statements_handler_process_stmt_var_annotation(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *defaultExpr, zval *nodeCallback); zv::Val pt_statements_handler_get_overriding_throw_points(zval *handler, zval *statement, zval *scope); zv::Val pt_statements_handler_emit_var_tag_changed_node(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *defaultExpr, zval *nodeCallback); +/* $handler->emitRedundantVarTagNodes($nodeScopeResolver, $scope, $storage, + * $stmt, $nodeCallback); false = pending exception */ +bool pt_statements_handler_emit_redundant_var_tag_nodes(zval *handler, zval *nodeScopeResolver, zval *scope, zval *storage, zval *stmt, zval *nodeCallback); zv::Val pt_statements_handler_get_variable_mention_flow(zval *handler, zval *stmt); /* StatementListWalkState.cpp — new StatementListWalkState($scope) / @@ -2898,6 +2901,9 @@ zv::Val pt_variable_control_flow_new(zend_string *kind, const pt_variable_contro extern zend_class_entry *pt_ce_var_annotation_processor; zv::Val pt_var_annotation_processor_process_var_annotation(zval *processor, zval *scope, zval *variableNames, zval *node, bool *changed); +/* $processor->processForeachIterateeVarAnnotation($scope, $variableName, + * $node), likewise */ +zv::Val pt_var_annotation_processor_process_foreach_iteratee_var_annotation(zval *processor, zval *scope, zend_string *variableName, zval *node); /* {{{ the function-call cluster (FunctionReflectionAccess.cpp, * OutputBufferHelper.cpp, FuncCallScopeEffectsHelper.cpp) */ diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index db61783b772..930ea1efb58 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -4323,6 +4323,7 @@ public function assignVariable(string $variableName, $type, $nativeType, $certai $vapScopes = [ 'file' => $vapRecordingScope($vehScope), 'class' => $vapRecordingScope($sfAssignScope), + 'int $a' => $vapRecordingScope($vehScope->assignVariable('a', new \PHPStan\Type\IntegerType(), new \PHPStan\Type\IntegerType(), \PHPStan\TrinaryLogic::createYes())), ]; $vapStmt = static function (array $comments): \PhpParser\Node\Stmt\Expression { return new \PhpParser\Node\Stmt\Expression(new \PhpParser\Node\Expr\Variable('a'), $comments === [] ? [] : ['comments' => $comments]); @@ -4347,6 +4348,18 @@ public function getComments(): array }, ]; +$vapForeachStmt = static fn (string $doc): \PhpParser\Node\Stmt\Foreach_ => new \PhpParser\Node\Stmt\Foreach_(new \PhpParser\Node\Expr\Variable('a'), new \PhpParser\Node\Expr\Variable('v'), [], ['comments' => [$vapDoc($doc)]]); +$vapForeachStmts = [ + 'named' => $vapForeachStmt('/** @var int $a */'), + 'positional' => $vapForeachStmt('/** @var non-empty-array */'), + 'other variable' => $vapForeachStmt('/** @var int $b */'), +]; +// what the redundant @var tag detector recorded: tag type and whether every +// application changed nothing, by statement and tag key +$vapRecorded = \Closure::bind(static fn (\PHPStan\Analyser\RedundantVarTagDetector $detector): array => array_map( + static fn (array $statement): array => array_map(static fn (array $tag): array => [$tag[0]->getType()->describe(\PHPStan\Type\VerbosityLevel::precise()), $tag[1]], $statement[1]), + $detector->applications, +), null, \PHPStan\Analyser\RedundantVarTagDetector::class); $vapNames = [ 'a' => ['a'], 'b' => ['b'], @@ -4356,9 +4369,11 @@ public function getComments(): array ]; $vapResults = []; foreach (['php' => \PHPStan\Analyser\VarAnnotationProcessor::class, 'native' => \PHPStanTurbo\VarAnnotationProcessor::class] as $side => $vapClass) { - $processor = new $vapClass($vapFileTypeMapper); + $vapDetector = new \PHPStan\Analyser\RedundantVarTagDetector(); + $processor = new $vapClass($vapFileTypeMapper, $vapDetector, true); $r = []; foreach ($vapScopes as $scopeLabel => $scope) { + $vapDetector->resetFileAnalysisState(); foreach ($vapStmts as $stmtLabel => $stmt) { foreach ($vapNames as $namesLabel => $names) { $label = $scopeLabel . ' / ' . $stmtLabel . ' / ' . $namesLabel; @@ -4384,6 +4399,12 @@ public function getComments(): array } } } + foreach ($vapForeachStmts as $stmtLabel => $stmt) { + $scope->assigned = []; + $result = $processor->processForeachIterateeVarAnnotation($scope, 'a', $stmt); + $r[$scopeLabel . ' / foreach ' . $stmtLabel] = [$result === $scope, $scope->assigned]; + } + $r[$scopeLabel . ' / recorded'] = array_values($vapRecorded($vapDetector)); } $vapResults[$side] = $r; } @@ -4400,6 +4421,13 @@ public function getComments(): array 'VarAnnotationProcessor: the fixture exercises every arm: ' . json_encode(array_intersect_key($vapResults['php'], array_flip(['class / two docs / a b / by ref', 'file / string comments / a / by ref', 'file / overriding node / a / without', 'file / plain comment / a / by ref', 'file / positional / a b / by ref', 'file / positional / keyed / by ref']))), ); check(($vapResults['php']['file / named / a / by ref'][1] ?? null) === true && ($vapResults['php']['class / positional / b / by ref'][2][0][1] ?? null) === 'string', 'VarAnnotationProcessor: the fixture assigns through named and positional tags: ' . json_encode([$vapResults['php']['file / named / a / by ref'] ?? null, $vapResults['php']['class / positional / b / by ref'] ?? null])); +check( + in_array(['int', true], array_merge(...array_map('array_values', $vapResults['php']['int $a / recorded'] ?? [[]])), true) + && in_array(['float', false], array_merge(...array_map('array_values', $vapResults['php']['int $a / recorded'] ?? [[]])), true) + && ($vapResults['php']['file / foreach other variable'] ?? null) === [true, []] + && count($vapResults['php']['file / foreach positional'][1] ?? []) === 1, + 'VarAnnotationProcessor: the fixture records unchanged and changed tags for the redundant @var tag detector: ' . json_encode(array_intersect_key($vapResults['php'], array_flip(['int $a / recorded', 'file / foreach other variable', 'file / foreach positional']))), +); // ---- the analyser value classes ---- // analyser-values.php builds the value objects on both sides from the same