Skip to content

Report abstract method overriding non-abstract method in OverridingMethodRule and OverridingPropertyRule#5730

Merged
ondrejmirtes merged 2 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-a8o53g5
May 22, 2026
Merged

Report abstract method overriding non-abstract method in OverridingMethodRule and OverridingPropertyRule#5730
ondrejmirtes merged 2 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-a8o53g5

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

PHP forbids making a non-abstract method abstract in a child class (it's a fatal error), but PHPStan did not report this. This PR adds the check in both OverridingMethodRule (for regular methods and constructors) and OverridingPropertyRule (for property hooks).

Changes

  • src/Rules/Methods/OverridingMethodRule.php: Added abstract-overriding-non-abstract check in two places:
    • In the main prototype check path (handles regular methods, static methods, protected methods, multi-level inheritance)
    • In the constructor-specific $prototypeData === null path, since MethodPrototypeFinder intentionally skips non-abstract constructor prototypes
  • src/Rules/Properties/OverridingPropertyRule.php: Added per-hook abstract-overriding-non-abstract check within the supportsPropertyHooks() block. When a child property is declared abstract and the prototype property has concrete hooks, each matching hook type is reported individually (matching PHP's per-hook error behavior).
  • Both errors use ->nonIgnorable() as instructed in the issue comments.
  • New error identifiers: method.abstractOverridingNonAbstract and property.abstractOverridingNonAbstractHook

Root cause

OverridingMethodRule checked for final methods, static/non-static mismatches, visibility changes, parameter contravariance, and return type covariance — but never checked whether an abstract method was trying to override a non-abstract one. The same gap existed in OverridingPropertyRule for property hooks.

Analogous cases probed

  • Constructors: PHP also forbids abstract constructors overriding non-abstract ones. MethodPrototypeFinder skips non-abstract constructor prototypes (by design, for signature compatibility), so the check was added separately in the constructor handling branch. ✅ Fixed
  • Trait abstract methods: PHP allows a trait's abstract method to coexist with a concrete inherited method — no error needed. ✅ Verified (no fix needed)
  • Interface methods: All interface methods are abstract, so an interface extending another interface can't trigger this. ✅ Verified (not applicable)
  • Non-hooked properties overridden by abstract hooked properties: PHP allows public int $x; to be overridden by abstract public int $x { get; } — no error needed. ✅ Verified (no fix needed)

Test

  • tests/PHPStan/Rules/Methods/data/bug-14457.php — Regression test for abstract method overriding non-abstract, covering: basic case, static methods, protected methods, multi-level inheritance, constructors, and OK cases (abstract→abstract, concrete→concrete, concrete→abstract).
  • tests/PHPStan/Rules/Properties/data/bug-14457.php — Regression test for abstract property hooks overriding non-abstract hooks, covering: get hook, set hook, both hooks, and OK cases (abstract→abstract, concrete→abstract, concrete→concrete).

Fixes phpstan/phpstan#14457

…ethodRule` and `OverridingPropertyRule`

- Add non-ignorable error in OverridingMethodRule when an abstract method overrides a non-abstract parent method
- Handle constructor special case separately since MethodPrototypeFinder skips non-abstract constructor prototypes
- Add non-ignorable error in OverridingPropertyRule when an abstract property with hooks overrides a non-abstract hooked property (per-hook reporting)
- Error message matches PHP's native fatal error: "Cannot make non abstract method X::Y() abstract in class Z"
- Cover static methods, protected methods, multi-level inheritance, and constructors in tests
Copy link
Copy Markdown
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

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

  1. tests/PHPStan/Rules/Properties/data/bug-14457.php needs to be excluded from make lint in Makefile
  2. There are escaped mutants https://github.com/phpstan/phpstan-src/actions/runs/26278594657/job/77349482802?pr=5730 which means more tests have to be added

- Add tests/PHPStan/Rules/Properties/data/bug-14457.php to make lint
  exclusion list (PHP 8.4 property hooks syntax)
- Change !$prototype->isAbstract()->yes() to $prototype->isAbstract()->no()
  in OverridingPropertyRule — equivalent for PhpPropertyReflection (always
  returns Yes or No), but killable by TrinaryLogicMutator since the reversed
  mutation (->yes()) is caught by the abstract-overriding-abstract test case

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

Pushed. Here's what was done to address the review:

  1. Lint exclusion: Added --exclude tests/PHPStan/Rules/Properties/data/bug-14457.php to the Makefile lint target, since that file uses PHP 8.4 property hooks syntax that parallel-lint can't parse.

  2. Escaped mutant fix: Changed !$prototype->isAbstract()->yes() to $prototype->isAbstract()->no() in OverridingPropertyRule.php:181. The TrinaryLogicMutator was mutating !->yes() to ->no() — which is semantically identical for PhpPropertyReflection (it always returns Yes or No, never Maybe), making the mutant unkillable by tests. With ->no() as the base code, the mutator will flip it to ->yes(), which IS distinguishable: when the prototype is abstract, ->yes() returns true (incorrectly entering the error branch), caught by the abstract→abstract test case.

@ondrejmirtes ondrejmirtes merged commit f8d7b8a into phpstan:2.1.x May 22, 2026
654 of 661 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-a8o53g5 branch May 22, 2026 10:53
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