Fix phpstan/phpstan#6189: Generator suspension points should be treated as infinite loop break points#5057
Merged
VincentLanglet merged 3 commits intophpstan:2.1.xfrom Mar 1, 2026
Conversation
- Generator suspension points (yield/yield from) act as break points,
so while(true) { yield $x; } is valid and not a true infinite loop
- Added hasYield flag to BreaklessWhileLoopNode and DoWhileLoopConditionNode
- WhileLoopAlwaysTrueConditionRule and DoWhileLoopConstantConditionRule
now skip the warning when the loop body contains a yield
- Loops without yield in a generator still correctly report the warning
Closes phpstan/phpstan#6189
VincentLanglet
approved these changes
Mar 1, 2026
staabm
approved these changes
Mar 1, 2026
Contributor
|
also fixes phpstan/phpstan#10054 |
staabm
approved these changes
Mar 1, 2026
staabm
reviewed
Mar 1, 2026
| * @param StatementExitPoint[] $exitPoints | ||
| */ | ||
| public function __construct(private While_ $originalNode, private array $exitPoints) | ||
| public function __construct(private While_ $originalNode, private array $exitPoints, private bool $hasYield) |
Contributor
There was a problem hiding this comment.
btw: I was not sure whether the yields should be within the exitPoints
Contributor
There was a problem hiding this comment.
I thought about it, but InternalStatementResult already differentiate $hasYield and $exitPoints so it seems consistent to me.
staabm
approved these changes
Mar 1, 2026
This was referenced Mar 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PHPStan incorrectly reported "While loop condition is always true" for infinite loops inside generators that contain
yieldstatements. Ayieldin a loop is effectively a suspension point — the generator pauses and can be abandoned by the caller, so the loop is not truly infinite. This is equivalent toInfiniteIteratorin PHP's SPL.Changes
$hasYieldparameter tosrc/Node/BreaklessWhileLoopNode.phpconstructor and ahasYield()getter$hasYieldparameter tosrc/Node/DoWhileLoopConditionNode.phpconstructor and ahasYield()gettersrc/Analyser/NodeScopeResolver.phpto pass$finalScopeResult->hasYield()/$bodyScopeResult->hasYield()when constructing these nodessrc/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.phpto suppress the warning when the loop body contains a yieldsrc/Rules/Comparison/DoWhileLoopConstantConditionRule.phpto suppress the always-true warning when the loop body contains a yieldRoot cause
The
WhileLoopAlwaysTrueConditionRuleandDoWhileLoopConstantConditionRulehad no awareness ofyieldstatements inside the loop body. They only checked forbreak,continue, andreturnas exit points, plus theneverreturn type on the enclosing function. Generator suspension viayieldwas not considered as a valid way to "exit" the loop iteration, leading to false positives.Test
Added
tests/PHPStan/Rules/Comparison/data/bug-6189.phpwith test cases covering:while(true) { yield 1; }— no warning (yield acts as suspension point)while(true) { yield from [...]; }— no warning (yield from also suspends)while(true) {}in a non-generator — still warns (truly infinite)yieldoutside the loop body withwhile(true) {}— still warns (yield doesn't help the loop)do { yield 1; } while(true);— no warning (do-while with yield)Fixes phpstan/phpstan#6189
Fixes phpstan/phpstan#10054