Fix phpstan/phpstan#13416: Incorrectly narrowed to previously asserted type, even if marked impure#5112
Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Closed
Conversation
- TypeSpecifier::createForExpr() checked if the top-level function call was pure before remembering its type, but did not check whether arguments contained impure sub-expressions - Added containsImpureCall() helper to recursively detect impure function/method/static calls within expression trees - Applied the check to FuncCall, MethodCall, and StaticCall branches in createForExpr() - Added regression test in tests/PHPStan/Analyser/nsrt/bug-13416.php Closes phpstan/phpstan#13416
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
assert(count(MyRecord::find()) === 1)narrowed the type ofcount(MyRecord::find())to1, subsequent calls tocount(MyRecord::find())incorrectly reused this narrowed type — even thoughMyRecord::find()is marked@phpstan-impureand could return different values on each call. This caused false positives like "comparison will always evaluate to false" when comparing the count to a different value after modifying state.Changes
containsImpureCall()private method tosrc/Analyser/TypeSpecifier.phpthat recursively checks whether an expression tree contains any impure function, method, or static method callscreateForExpr(), after verifying the top-level call is pure, now also checks if the call's arguments contain impure sub-expressions before remembering the narrowed typeRoot cause
TypeSpecifier::createForExpr()determines whether an expression's narrowed type should be remembered in the scope. For function calls likecount(X), it checked ifcount()itself was pure (it is), but did not check ifXcontained impure calls. SinceMyRecord::find()is marked@phpstan-impure, the expressioncount(MyRecord::find())should not have its type remembered across different evaluation points — each call toMyRecord::find()can return a different array, so the count can differ.The fix adds a recursive check of all sub-expressions within the arguments of a pure function call. If any sub-expression is an impure function/method/static call, the entire expression's type is not remembered.
Test
Added
tests/PHPStan/Analyser/nsrt/bug-13416.phpwhich reproduces the issue: afterassert(count(MyRecord::find()) === 1)and an impureinsert()call,count(MyRecord::find())should beint<0, max>(not1).Fixes phpstan/phpstan#13416