Skip to content

Invalidate maybe-impure function return values after impure method/static calls#5667

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-vf3uivq
Open

Invalidate maybe-impure function return values after impure method/static calls#5667
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-vf3uivq

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

  • After assert(count(MyRecord::find()) === 1), calling an impure method like $msg2->insert() now correctly invalidates the narrowed count, so count(MyRecord::find()) returns int<0, max> instead of staying narrowed to 1
  • Added MutatingScope::invalidateAllMaybeImpureFunctionReturnValues() which removes stored expression types that contain maybe-impure function/method/static calls
  • Hooked invalidation into MethodCallHandler and StaticCallHandler for calls with hasSideEffects()->yes(), skipping $this-> calls to avoid over-invalidation

Details

The root cause was that specifyTypesForCountFuncCall() narrows the argument expression (e.g., MyRecord::find() -> array{0: MyRecord}) and stores it in scope. Since find() has hasSideEffects()->maybe() and rememberPossiblyImpureFunctionValues is enabled, the narrowing persists. When $msg2->insert() runs, invalidateExpression() only invalidates expressions containing $msg2, so MyRecord::find() was never cleared.

The fix adds a new method that walks all stored expression types with NodeFinder, checking each for function calls, method calls, or static calls that are not proven pure (hasSideEffects()->no()). Any expression containing such a call is removed from the scope.

Test plan

  • Added tests/PHPStan/Analyser/nsrt/bug-13416.php with test cases covering:
    • Static call invalidated by method call
    • Method call invalidated by method call
    • strlen() of impure call invalidated by method call
    • Count NOT invalidated by pure function (rand)
    • Same scenarios inside a class context
  • Full test suite passes (12071 tests, 79697 assertions)
  • PHPStan self-analysis introduces no new errors
  • CS-fix reports no violations

Closes phpstan/phpstan#13416

…atic calls

When a maybe-impure function call's return value was narrowed via assert()
(e.g. assert(count(MyRecord::find()) === 1)), the narrowed type persisted
even after an impure method call like $msg2->insert(). This happened because
invalidateExpression() only invalidated expressions containing the specific
callee variable, not unrelated maybe-impure expressions whose results could
have been affected by the side effects.

Add invalidateAllMaybeImpureFunctionReturnValues() to MutatingScope which
walks stored expression types and removes any that contain maybe-impure
function/method/static calls. Call it from MethodCallHandler and
StaticCallHandler when processing calls with definite side effects
(hasSideEffects()->yes()), skipping $this-> calls which already invalidate
via invalidateExpression().

Closes phpstan/phpstan#13416
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.

2 participants