Skip to content

Fix phpstan/phpstan#12686: Unable to mark an anonymous function as impure#5348

Merged
VincentLanglet merged 5 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-q3t395n
Mar 30, 2026
Merged

Fix phpstan/phpstan#12686: Unable to mark an anonymous function as impure#5348
VincentLanglet merged 5 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-q3t395n

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented Mar 30, 2026

Summary

When a closure variable like $f was called in a condition (if ($f())), PHPStan incorrectly narrowed the type of subsequent $f() calls inside the branch, treating the closure as pure even when it had impure points. This caused $f() to be inferred as true instead of bool inside the truthy branch.

Changes

  • Added purity check for non-named FuncCall expressions (callable variable calls) in src/Analyser/TypeSpecifier.php createForExpr() method
  • The check mirrors the existing purity check for named function calls (lines 2213-2232), using CallableParametersAcceptor::isPure() to determine if the callable has side effects
  • New regression test in tests/PHPStan/Analyser/nsrt/bug-12686.php

Root cause

TypeSpecifier::createForExpr() already had purity checks for named function calls ($expr->name instanceof Name), method calls, and static calls. When a function had side effects, the method returned empty SpecifiedTypes, preventing type narrowing from caching the call result. However, callable variable calls ($f() where $expr->name is an Expr) were missing this check entirely. This meant closures that called impure functions like rand() still had their return types cached/narrowed across calls.

Test

Added tests/PHPStan/Analyser/nsrt/bug-12686.php which verifies:

  1. An impure closure ($f calling rand()) returns bool (not true) when called again inside an if ($f()) block
  2. A pure closure ($h returning true) still correctly narrows to true inside the if block

Fixes phpstan/phpstan#12686
Fixes phpstan/phpstan#3770
Fixes phpstan/phpstan#6822

- Added purity check for callable variable calls ($f()) in TypeSpecifier::createForExpr()
- Previously, only named function calls (e.g. rand()) had purity checks that prevented
  type narrowing for impure functions; callable variable calls were missing this check
- New regression test in tests/PHPStan/Analyser/nsrt/bug-12686.php
Tests the code path where a variable holds a union of closures
(multiple callable parameter acceptors): one test with mixed
pure/impure closures verifying no narrowing, and one with all
pure closures verifying narrowing still works.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

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

add a regression test for

Both issues report that closures cannot be marked as impure via PHPDoc.
These tests verify that @phpstan-impure on closures prevents incorrect
type narrowing in conditional branches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. Added regression tests for both issues:

  • tests/PHPStan/Analyser/nsrt/bug-3770.php — Tests that @phpstan-impure on a closure prevents type narrowing in conditionals, and that @phpstan-pure closures still narrow correctly.
  • tests/PHPStan/Analyser/nsrt/bug-6822.php — Tests that @phpstan-impure closures returning bool and int are not narrowed after conditional checks.

Both make tests and make phpstan pass.

@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace Bug6822;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bug 6822 was about a false-positive rule error. we need a rule test for this file to prove it no longer errors.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done. Added a rule test for bug 6822 in IfConstantConditionRuleTest that verifies impure closures (@phpstan-impure) don't produce false-positive "If condition is always true" errors when used in nested conditions. The test data file at tests/PHPStan/Rules/Comparison/data/bug-6822.php covers both bool-returning and int-returning impure closures.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

re-use the tests/PHPStan/Analyser/nsrt/bug-6822.php for the new rule-test instead of adding another new one

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Full test suite passed too. All done — the review feedback has been addressed, tests and static analysis are green, and the commit is pushed.

phpstan-bot and others added 2 commits March 30, 2026 08:27
…e error

The nsrt test only verified type inference. This adds an IfConstantConditionRule
test to prove that impure closures don't cause false "If condition is always true"
errors when used in nested conditions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm requested a review from VincentLanglet March 30, 2026 09:10
@VincentLanglet VincentLanglet merged commit 1920006 into phpstan:2.1.x Mar 30, 2026
680 of 686 checks passed
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-q3t395n branch March 30, 2026 09:23
@canvural
Copy link
Copy Markdown
Contributor

This doesn't really fix phpstan/phpstan#3770 right?

@ondrejmirtes
Copy link
Copy Markdown
Member

It does not, sorry

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.

5 participants