Skip to content

Respect @phpstan-all-methods-impure, @phpstan-all-methods-pure, and method-level purity annotations on built-in classes#5539

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-6929pj4
Closed

Respect @phpstan-all-methods-impure, @phpstan-all-methods-pure, and method-level purity annotations on built-in classes#5539
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-6929pj4

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When creating method reflections for built-in PHP classes (like Memcached), PhpClassReflectionExtension::createMethod() takes a special path for classes that are built-in or enums and have method signatures in the signature map. This path determined hasSideEffects solely from the signature map metadata, completely ignoring PHPDoc purity annotations (@phpstan-all-methods-impure, @phpstan-all-methods-pure, @phpstan-pure, @phpstan-impure) from stubs.

This meant that adding a stub like:

/** @phpstan-all-methods-impure */
class Memcached {}

had no effect on built-in classes whose methods exist in the signature map, causing false positives when PHPStan cached/narrowed method return values across calls.

Changes

  • Added purity annotation checks in the built-in class path of PhpClassReflectionExtension::createMethod() (src/Reflection/Php/PhpClassReflectionExtension.php):
    1. Method-level @phpstan-pure/@phpstan-impure from $currentResolvedPhpDoc->isPure()
    2. Class-level @phpstan-all-methods-pure/@phpstan-all-methods-impure from $declaringClass->getResolvedPhpDoc()
    3. Falls back to signature map metadata / TrinaryLogic::createMaybe() as before
  • Initialized $currentResolvedPhpDoc before the method signature loop to fix an undefined variable warning

Root cause

The built-in class path in createMethod() (guarded by $declaringClass->isBuiltin() || $declaringClass->isEnum()) only consulted $this->signatureMapProvider->hasMethodMetadata() for side-effect information. The userland path (createUserlandMethodReflection()) already had the correct logic at lines 908-932 checking both method-level and class-level purity annotations. The fix replicates this pattern in the built-in path.

Test

  • tests/PHPStan/Rules/Comparison/Bug14534Test.php: Regression test for the reported bug — @phpstan-all-methods-impure on Memcached via stub, verifying no false notIdentical.alwaysFalse error on getResultCode() in a do-while loop
  • tests/PHPStan/Rules/Pure/Bug14534AllPureBuiltinTest.php: Analogous test for @phpstan-all-methods-pure on a built-in class — verifying a pure function can call methods without "Possibly impure call" errors
  • tests/PHPStan/Rules/Comparison/Bug14534MethodImpureTest.php: Test for method-level @phpstan-impure on a specific built-in method via stub

All three tests fail without the fix and pass with it.

Fixes phpstan/phpstan#14534

…nd method-level purity annotations on built-in classes

- In PhpClassReflectionExtension::createMethod(), the built-in class path
  (line 614 condition) determined hasSideEffects solely from signature map
  metadata, ignoring PHPDoc purity annotations from stubs
- Added checks for method-level @phpstan-pure/@phpstan-impure from
  $currentResolvedPhpDoc->isPure(), then class-level
  @phpstan-all-methods-pure/@phpstan-all-methods-impure from
  $declaringClass->getResolvedPhpDoc()
- This mirrors the existing logic in createUserlandMethodReflection()
  (lines 908-932) which correctly handles these annotations
- Initialized $currentResolvedPhpDoc before the method signature loop to
  avoid undefined variable when the loop doesn't execute
@ondrejmirtes
Copy link
Copy Markdown
Member

Personally I think the tests should put the annotations above some non-sensical class we'll never do it for everyone, like SplFileObject or something.

Once we register a stub file for Memcached for everyone, all the tests lose their meaning.

@VincentLanglet
Copy link
Copy Markdown
Contributor

I took the fix and the tests (with SplFileStorage) in #5536

@staabm staabm deleted the create-pull-request/patch-6929pj4 branch April 26, 2026 12:19
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.

3 participants