Skip to content

Report impure method overriding pure parent method in MethodSignatureRule#5584

Open
phpstan-bot wants to merge 10 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-zcama98
Open

Report impure method overriding pure parent method in MethodSignatureRule#5584
phpstan-bot wants to merge 10 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-zcama98

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a parent method is marked @phpstan-pure, child classes could override it with @phpstan-impure without any error being reported. This violated the Liskov Substitution Principle: callers expecting a pure method would get an impure one. This PR adds a purity compatibility check to MethodSignatureRule.

Changes

  • src/Rules/Methods/MethodSignatureRule.php: Added a check at the beginning of the parent method loop — when the child method's isPure() is no and the parent method's isPure() is yes, report an error with identifier method.impureOverridePure
  • tests/PHPStan/Rules/Methods/data/bug-14563.php: New test data file covering all parent method sources and edge cases
  • tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php: Added testBug14563() test method

Root cause

MethodSignatureRule checked return type covariance and parameter type contravariance for overriding methods, but had no check for purity compatibility. An impure child method could silently override a pure parent method, breaking the purity contract that callers rely on.

Test

The regression test covers these cases (all producing errors):

  • Class extends class: child has @phpstan-impure, parent has @phpstan-pure
  • Interface implementation: implementing class marks method @phpstan-impure while interface declares it @phpstan-pure
  • @phpstan-all-methods-pure on parent class: child class marks method @phpstan-impure
  • Abstract trait method: trait declares @phpstan-pure abstract method, using class marks it @phpstan-impure
  • Grandchild inheritance: grandchild overrides with @phpstan-impure a method pure in the parent chain
  • Multiple interfaces: both interfaces declare the method @phpstan-pure, implementing class marks it @phpstan-impure — error reported for each interface

And these non-error cases (verifying no false positives):

  • Child with no annotation overriding pure parent (inherits purity)
  • Child with @phpstan-pure overriding pure parent
  • Child with @phpstan-pure overriding impure parent (safe — more restrictive)
  • Child with @phpstan-impure overriding unannotated parent
  • Child in @phpstan-all-methods-impure class overriding pure parent (PHPDoc inheritance of @phpstan-pure takes precedence over class-level annotation)

Fixes phpstan/phpstan#14563

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

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

Add test for static method too.

@VincentLanglet VincentLanglet self-assigned this May 2, 2026
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. Added static method test cases covering:

  • Error case: Impure static method overriding a pure static parent method (StaticChildImpureOverridesPure extends StaticFoo)
  • Error case: Impure static method implementing a pure static interface method (StaticImpureImplementation implements StaticPureInterface)
  • No-error cases: Static child with no annotation overriding pure parent, static child with @phpstan-pure overriding pure parent, and static child with @phpstan-impure overriding unannotated parent

All tests pass and make phpstan reports no errors.

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

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

Why tests are failing on PHP 7.4 ?

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

The PHP 7.4 test failure is fixed. The issue was that collectParentMethods doesn't detect abstract trait methods on PHP 7.4 — this is a known limitation, consistent with other trait-related tests in MethodSignatureRuleTest that use #[RequiresPhp('>= 8.0.0')] (like testTraits, testBug10166, testBug10184, testOverridenAbstractTraitMethodPhpDoc).

The fix makes the trait abstract method error expectation conditional on PHP_VERSION_ID >= 80000. The other integration test failures (PMMP, doctrine/orm, shopware) are unrelated to this PR — they're caused by other recent changes to main.

Comment thread src/Rules/Methods/MethodSignatureRule.php Outdated
Comment thread tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php
$method->getName(),
$parentMethodDeclaringClass->getDisplayName(),
$parentMethod->getName(),
))->identifier('method.purity')->build();
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.

Identifier must be method.impure.

Also this new error should be behind a dedicated toggle which is enabled behind bleedingedge

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.

Both review comments addressed and pushed:

  1. Identifier changed: method.puritymethod.impure
  2. Toggle added: New checkMethodPurityOverride feature toggle (defaults to false, enabled in bleedingEdge.neon). The check in MethodSignatureRule is now gated behind $this->checkPurity, which is wired to %featureToggles.checkMethodPurityOverride% in services.neon.

All 11,987 tests pass and make phpstan reports no errors.

Comment thread conf/parametersSchema.neon Outdated
reportNestedTooWideType: bool()
assignToByRefForeachExpr: bool()
curlSetOptArrayTypes: bool()
checkMethodPurityOverride: bool()
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.

Change for reportMethodPurityOverride

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.

Renamed checkMethodPurityOverride to reportMethodPurityOverride across all four config files (bleedingEdge.neon, config.neon, parametersSchema.neon, services.neon). All 11,987 tests pass and make phpstan reports no errors.

Comment thread conf/services.neon Outdated
reportMaybes: %reportMaybesInMethodSignatures%
reportStatic: %reportStaticMethodSignatures%
checkPurity: %featureToggles.checkMethodPurityOverride%
checkPurity: %featureToggles.reportMethodPurityOverride%
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.

This should be also reportMethodPurityOverride

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.

Renamed the checkPurity constructor parameter and service argument to reportMethodPurityOverride for consistency with the config toggle name. All tests pass (43/43) and make phpstan reports no errors.

VincentLanglet and others added 9 commits May 5, 2026 20:37
…eRule`

- Add purity compatibility check in MethodSignatureRule::processNode()
- When a parent method has @phpstan-pure and the child method has @phpstan-impure, report an error with identifier method.impureOverridePure
- The check runs for all parent sources: parent classes, interfaces, and abstract trait methods
- Covers @phpstan-all-methods-pure on parent class (purity is inherited per-method)
- Also covers grandchild inheritance and multiple interface implementation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trait abstract method detection in collectParentMethods does not work
on PHP 7.4, consistent with other trait-related tests in this file
that use #[RequiresPhp('>= 8.0.0')].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…bled in bleedingEdge

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-zcama98 branch from 84fb578 to e0144c9 Compare May 5, 2026 18:39
class ChildImpureOverridesPure extends Foo
{

/** @phpstan-impure */
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.

we should also test the case of a method returning :void (implicit impure) beeing overridden by a pure/impure method

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 test cases for :void return type methods covering:

  • Error case: Child with @phpstan-impure overriding a @phpstan-pure void parent method (ChildImpureOverridesPureVoid)
  • No-error cases:
    • Child with @phpstan-pure overriding a pure void parent
    • Child with no annotation overriding a pure void parent
    • Child with @phpstan-impure overriding an unannotated void parent
    • Child with @phpstan-pure overriding an unannotated void parent

All 11,993 tests pass and make phpstan reports no errors.

Test that methods returning :void (implicitly impure) are handled
correctly when overriding pure/impure parent methods and when
being overridden by pure/impure child methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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