Skip to content

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
phpstan-bot:create-pull-request/patch-ai35gm1
Mar 1, 2026
Merged

Fix phpstan/phpstan#6189: Generator suspension points should be treated as infinite loop break points#5057
VincentLanglet merged 3 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ai35gm1

Conversation

@phpstan-bot
Copy link
Collaborator

@phpstan-bot phpstan-bot commented Feb 27, 2026

Summary

PHPStan incorrectly reported "While loop condition is always true" for infinite loops inside generators that contain yield statements. A yield in 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 to InfiniteIterator in PHP's SPL.

Changes

  • Added $hasYield parameter to src/Node/BreaklessWhileLoopNode.php constructor and a hasYield() getter
  • Added $hasYield parameter to src/Node/DoWhileLoopConditionNode.php constructor and a hasYield() getter
  • Updated src/Analyser/NodeScopeResolver.php to pass $finalScopeResult->hasYield() / $bodyScopeResult->hasYield() when constructing these nodes
  • Updated src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php to suppress the warning when the loop body contains a yield
  • Updated src/Rules/Comparison/DoWhileLoopConstantConditionRule.php to suppress the always-true warning when the loop body contains a yield

Root cause

The WhileLoopAlwaysTrueConditionRule and DoWhileLoopConstantConditionRule had no awareness of yield statements inside the loop body. They only checked for break, continue, and return as exit points, plus the never return type on the enclosing function. Generator suspension via yield was not considered as a valid way to "exit" the loop iteration, leading to false positives.

Test

Added tests/PHPStan/Rules/Comparison/data/bug-6189.php with 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)
  • yield outside the loop body with while(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

phpstan-bot and others added 2 commits February 27, 2026 12:48
- 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 VincentLanglet requested a review from staabm March 1, 2026 15:50
@staabm
Copy link
Contributor

staabm commented Mar 1, 2026

also fixes phpstan/phpstan#10054

@staabm staabm changed the title Fix #6189: Generator suspension points should be treated as infinite loop break points Fix phpstan/phpstan#6189: Generator suspension points should be treated as infinite loop break points 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw: I was not sure whether the yields should be within the exitPoints

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it, but InternalStatementResult already differentiate $hasYield and $exitPoints so it seems consistent to me.

@VincentLanglet VincentLanglet merged commit 925f29c into phpstan:2.1.x Mar 1, 2026
634 of 647 checks passed
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-ai35gm1 branch March 8, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants