From 0e30731c3969d205563151fb4b708cd00a620b2f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 31 Jul 2026 14:00:03 +0200 Subject: [PATCH 1/3] Memoize the isGenerator() yield scan on the AST node The scan walks the whole function body and reflections for the same node are recreated per ask, so the answer is memoized on the node itself and lives and dies with the parser-cached AST. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7 --- .../PhpFunctionFromParserNodeReflection.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 0c94a5082c7..ef46fe63ac1 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -277,7 +277,18 @@ public function isBuiltin(): bool public function isGenerator(): bool { - return $this->nodeIsOrContainsYield($this->functionLike); + // the yield scan walks the whole body; reflections for the same node are + // recreated per ask, so the answer is memoized on the AST node itself + // and lives and dies with the parser-cached AST + $cached = $this->functionLike->getAttribute('phpstanIsGenerator'); + if ($cached !== null) { + return $cached; + } + + $isGenerator = NodeScanner::nodeIsOrContainsYield($this->functionLike); + $this->functionLike->setAttribute('phpstanIsGenerator', $isGenerator); + + return $isGenerator; } public function acceptsNamedArguments(): TrinaryLogic @@ -285,11 +296,6 @@ public function acceptsNamedArguments(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } - private function nodeIsOrContainsYield(Node $node): bool - { - return NodeScanner::nodeIsOrContainsYield($node); - } - public function getAsserts(): Assertions { return $this->assertions; From 6166aaa505a7b286f2f2fbe21e572f9a33e5b16a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 31 Jul 2026 14:00:05 +0200 Subject: [PATCH 2/3] Check yield operand voidness through getKeepVoidType() Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7 --- src/Rules/Generators/YieldFromTypeRule.php | 2 +- src/Rules/Generators/YieldTypeRule.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/Generators/YieldFromTypeRule.php b/src/Rules/Generators/YieldFromTypeRule.php index b77f04b53a4..a0102c5c9e1 100644 --- a/src/Rules/Generators/YieldFromTypeRule.php +++ b/src/Rules/Generators/YieldFromTypeRule.php @@ -137,7 +137,7 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('generator.sendType')->build(); } - if (!$scope->isInFirstLevelStatement() && $scope->getType($node)->isVoid()->yes()) { + if (!$scope->isInFirstLevelStatement() && $scope->getKeepVoidType($node)->isVoid()->yes()) { $messages[] = RuleErrorBuilder::message('Result of yield from (void) is used.') ->identifier('generator.void') ->build(); diff --git a/src/Rules/Generators/YieldTypeRule.php b/src/Rules/Generators/YieldTypeRule.php index e5f94c81932..0f42eecd120 100644 --- a/src/Rules/Generators/YieldTypeRule.php +++ b/src/Rules/Generators/YieldTypeRule.php @@ -86,7 +86,7 @@ public function processNode(Node $node, Scope $scope): array ->identifier('generator.valueType') ->build(); } - if (!$scope->isInFirstLevelStatement() && $scope->getType($node)->isVoid()->yes()) { + if (!$scope->isInFirstLevelStatement() && $scope->getKeepVoidType($node)->isVoid()->yes()) { $messages[] = RuleErrorBuilder::message('Result of yield (void) is used.') ->identifier('generator.void') ->build(); From 72009348605f3ce7294a01bb8254832e28db8163 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 31 Jul 2026 14:00:05 +0200 Subject: [PATCH 3/3] Drop redundant @internal on hasAcceptorTemplateOrLateResolvableParameterType Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7 --- src/Reflection/ParametersAcceptorSelector.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 2eb2f885944..d3d1773f802 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -592,9 +592,6 @@ public static function hasAcceptorTemplateOrLateResolvableType(ParametersAccepto return self::hasAcceptorTemplateOrLateResolvableParameterType($acceptor); } - /** - * @internal - */ public static function hasAcceptorTemplateOrLateResolvableParameterType(ParametersAcceptor $acceptor): bool { foreach ($acceptor->getParameters() as $parameter) {