Use getType() instead of getNativeType() for loop iteration detection when treatPhpDocTypesAsCertain is false#5524
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
…tion when `treatPhpDocTypesAsCertain` is false - Change `for` loop `$isIterableAtLeastOnce` to always use `getType()` for condition evaluation, matching `foreach` behavior which already uses `getType()` unconditionally - Apply the same fix to `while` loop `$beforeCondBooleanType` and `$condBooleanType` (used for `$isIterableAtLeastOnce` and `$alwaysIterates`) - Apply the same fix to `do-while` loop `$condBooleanType` (used for `$alwaysIterates`) - The `for` loop's own `$alwaysIterates` already used `getType()` unconditionally, making the `$isIterableAtLeastOnce` switch inconsistent - `foreach` was already correct — it uses `$scope->getType()` for `isIterableAtLeastOnce()` detection - `if/elseif` left unchanged — those control branch reachability rather than scope merging
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
When
treatPhpDocTypesAsCertainis set tofalse,forandwhileloops usedgetNativeType()to evaluate whether the loop condition is always true (determining$isIterableAtLeastOnce). Since built-in functions likemax()returnmixedas their native type, a condition like0 <= max(0, $n)evaluated toboolinstead oftrue, making PHPStan think the loop might not execute. This caused the pre-loop scope (with e.g.$total = 0) to be merged into the post-loop scope, widening types incorrectly.Changes
src/Analyser/NodeScopeResolver.php: Changed$isIterableAtLeastOncedetermination inforloops (line ~1665) to always usegetType()instead of switching based ontreatPhpDocTypesAsCertainsrc/Analyser/NodeScopeResolver.php: Changed$beforeCondBooleanTypeinwhileloops (line ~1445) to always usegetType()— this variable is used for both the "never enters" check and$isIterableAtLeastOncesrc/Analyser/NodeScopeResolver.php: Changed$condBooleanTypeinwhileloops (line ~1496) to always usegetType()— used for$alwaysIteratesand$neverIteratessrc/Analyser/NodeScopeResolver.php: Changed$condBooleanTypeindo-whileloops (line ~1594) to always usegetType()— used for$alwaysIteratesAnalogous cases probed
foreachloop: Already uses$scope->getType()forisIterableAtLeastOnce()— no fix neededforloop$alwaysIterates: Already uses$bodyScope->getType()unconditionally — no fix neededif/elseifstatements: Also switch based ontreatPhpDocTypesAsCertain, but these control branch reachability (different concern) — left unchangeddo-whileloop: Fixed for consistency, as$alwaysIterateswas using the same conditional patternRoot cause
The
treatPhpDocTypesAsCertainflag was designed to control error reporting — whether errors like "comparison is always true" should be reported when the type comes from PHPDoc. However, it was also being used to control flow analysis (scope merging after loops), where using native types is too imprecise because built-in functions likemax()have native return typemixedeven though PHPStan's type extensions know the precise return type.The inconsistency was visible within the same code: the
forloop's$alwaysIteratesalready usedgetType()unconditionally, while$isIterableAtLeastOnceswitched based on the flag. Andforeachalways usedgetType()for its equivalent check.Test
tests/PHPStan/Analyser/Bug14522Test.php+tests/PHPStan/Analyser/data/bug-14522.php+tests/PHPStan/Analyser/bug-14522.neon: Regression test withtreatPhpDocTypesAsCertain: falsethat verifies correct type inference for$totalafterforandwhileloops that are guaranteed to execute at least oncetests/PHPStan/Analyser/nsrt/bug-14522.php: NSRT test with default settings coveringforloop always-enters, never-enters, maybe-enters, andwhileloop always-enters and maybe-entersFixes phpstan/phpstan#14522