Do not report abstract interface methods provided by a non-abstract built-in parent class in MissingMethodImplementationRule#6059
Open
phpstan-bot wants to merge 1 commit into
Conversation
…uilt-in parent class in `MissingMethodImplementationRule` - Skip an abstract method whose declaring class is an interface when a non-abstract built-in (internal) ancestor class implements that interface. Built-in classes always provide their interface methods at runtime, so any "abstract" appearance is a stub artifact of PHP-version gating. - Fixes the intermittent false positive on `SimpleXMLElement` subclasses (`RecursiveIterator::getChildren()`/`hasChildren()` gated at PHP 8.0) and the deterministic sibling on the `Intl*BreakIterator` family (`IteratorAggregate::getIterator()` gated at PHP 8.0) targeting PHP < 8.0. - Genuine cases are unaffected: methods declared in interfaces the user class implements itself, and abstract methods declared in built-in abstract classes (e.g. `FilterIterator::accept()`), are still reported.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MissingMethodImplementationRuleintermittently reported false positives such as:for a class extending
\SimpleXMLElement(mostly seen on PHP 7.4 CI,MissingMethodImplementationRuleTest::testBug3958).The rule walks the native reflection methods of a non-abstract class and reports every method that reflection marks as
abstract. When a class extends a built-in class, the built-in parent implements its interface methods natively at runtime — but PHP-version-gated stubs can present a built-in class as implementing an interface while the interface's methods are filtered out for the target PHP version. That inconsistent (interface present, method filtered) reflection state makes an inherited interface method look unimplemented, producing a false "abstract method" error.The fix teaches the rule to ignore an abstract method when a non-abstract built-in ancestor class implements the declaring interface, because such a parent always provides the method at runtime.
Changes
src/Rules/Methods/MissingMethodImplementationRule.phpisProvidedByBuiltinAncestor()walksClassReflection::getParents()and checksisBuiltin(),!isAbstract(),implementsInterface()).tests/PHPStan/Rules/Methods/MissingMethodImplementationRulePhp74Test.php+data/bug-14964.php+data/missing-method-impl-php74.neonphpVersion: 70400covering both the reported\SimpleXMLElementcase and the deterministic sibling\IntlBreakIteratorcase.Root cause
The bug is a family: a non-abstract class extending a non-abstract built-in class whose interface method is version-gated in the stubs. The stub keeps the
implements Xon the built-in class but filters the version-gated method body, so reflection yields an inconsistent state where the inherited interface method appears abstract.Instances found and covered by the single fix:
\SimpleXMLElementimplementsRecursiveIterator;getChildren()/hasChildren()are@since 8.0— the originally reported (intermittent) case.\IntlBreakIterator(and its subclasses\IntlRuleBasedBreakIterator,\IntlCodePointBreakIterator) implementIteratorAggregate;getIterator()is@since 8.0— a deterministic sibling reproducible atphpVersion < 8.0(no BetterReflection special-case masks it, unlikeSimpleXMLElement/SplObjectStorage).The fix is general: instead of relying on per-class stub special-cases, it trusts that any non-abstract built-in ancestor implementing the interface provides its methods. Verified that genuine errors are preserved — abstract methods from built-in abstract classes (
\FilterIterator::accept()) and from interfaces the user class implements directly are still reported.Test
MissingMethodImplementationRulePhp74Test::testBug14964analyses a class extending\IntlBreakIteratorand a class extending\SimpleXMLElementunderphpVersion: 70400and expects no errors. It fails without the fix (Non-abstract class Bug14964\MyBreakIterator contains abstract method getIterator() from interface IteratorAggregate.) and passes with it.MissingMethodImplementationRuleTest(including the genuine positive cases and enum case) continues to pass.make phpstanand the full test suite pass.Fixes phpstan/phpstan#14964