Do not remember an equality assertion statement as true when its arguments contain an impure call - #6628
Closed
phpstan-bot wants to merge 1 commit into
Closed
Do not remember an equality assertion statement as true when its arguments contain an impure call#6628phpstan-bot wants to merge 1 commit into
true when its arguments contain an impure call#6628phpstan-bot wants to merge 1 commit into
Conversation
…guments contain an impure call - ExpressionHandler stored the result of a void `@phpstan-assert =T` call statement as `true`, so that a duplicate assertion is reported as always true. It did this with assignExpression(), which skips the purity check that TypeSpecifier::create() applies. A repeated `assertSame($key, $counter->next())` or `assertSame($key, random_int(1, 10))` was then wrongly reported as alreadyNarrowedType. - Add TypeSpecifier::callOperandsContainNonPureCall(). It reuses the existing non-pure-call search on the call's arguments and on what the call is made on, without the call itself. The void assertion call is usually impure itself. - Only store `true` for the statement when that check passes. Function, method and static method assertions are all covered. Repeated assertions with pure arguments are still reported. - Probed the bool-returning `@phpstan-assert-if-true =T` variant in a condition (filterByTruthyValue path). It is already correct because it goes through TypeSpecifier::create(), which rejects impure expressions.
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
Since 2.2.15, calling a templated
@phpstan-assert =ExpectedType $actualassertion (like PHPUnit'sassertSame()) twice with an impure call as an argument reported the second call asalreadyNarrowedType. An example isassertSame($key, $counter->next())wherenext()is@phpstan-impure, orrandom_int(). This PR stops PHPStan from remembering the assertion'strueoutcome when the call's arguments contain a call that isn't known to be pure.Changes
src/Analyser/TypeSpecifier.php: addcallOperandsContainNonPureCall(). It runs the existing non-pure-call search (findNonPureCall, now split so the sub-node walk can be reused) over the call's arguments and over what it is called on, without the call itself.src/Analyser/StmtHandler/ExpressionHandler.php: onlyassignExpression()the equality assertion statement astruewhen its operands contain no non-pure call.$expectedor$actual). Both positions are covered.@phpstan-assert-if-true =Tvariant used in a condition. It was already correct, becauseMutatingScope::filterByTruthyValue()stores the result throughTypeSpecifier::create(), which already refuses expressions containing non-pure calls.Root cause
Commit 14ad31b added storing a void equality-assertion statement's result as
true, so that an exact duplicate assertion is reported as always true. Unlike the truthy/falsey path, it usedassignExpression()directly. That bypasses the purity checkTypeSpecifier::create()normally applies before remembering an expression's value. Because the expression key ofassertSame($key, $counter->next())is identical for both calls, the second call looked up the rememberedtrue. The whole call can't be checked with the regular purity check, because a void assertion function is itself considered impure. So the fix checks only the call's operands.Test
tests/PHPStan/Rules/Comparison/data/bug-15328.phpcontains the reproducer from the issue (impure method andrandom_int()), with an impure$expectedargument, plus the same scenarios for a static-method and an instance-method assertion. It is analysed byImpossibleCheckTypeFunctionCallRuleTest,ImpossibleCheckTypeMethodCallRuleTestandImpossibleCheckTypeStaticMethodCallRuleTest. Each file also has a duplicate assertion with pure arguments, which is still reported. All three tests fail without the fix.Fixes phpstan/phpstan#15328