From 86209b4cc0a0616159232cf30375662a34b51f36 Mon Sep 17 00:00:00 2001 From: staabm <120441+staabm@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:12:24 +0000 Subject: [PATCH] Do not report abstract interface methods provided by a non-abstract built-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. --- .../MissingMethodImplementationRule.php | 29 +++++++++++++++++ ...ssingMethodImplementationRulePhp74Test.php | 31 +++++++++++++++++++ .../PHPStan/Rules/Methods/data/bug-14964.php | 11 +++++++ .../data/missing-method-impl-php74.neon | 2 ++ 4 files changed, 73 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/MissingMethodImplementationRulePhp74Test.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-14964.php create mode 100644 tests/PHPStan/Rules/Methods/data/missing-method-impl-php74.neon diff --git a/src/Rules/Methods/MissingMethodImplementationRule.php b/src/Rules/Methods/MissingMethodImplementationRule.php index 3f4d7dd136e..534a5971068 100644 --- a/src/Rules/Methods/MissingMethodImplementationRule.php +++ b/src/Rules/Methods/MissingMethodImplementationRule.php @@ -7,6 +7,7 @@ use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\InClassNode; +use PHPStan\Reflection\ClassReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; @@ -47,6 +48,17 @@ public function processNode(Node $node, Scope $scope): array $declaringClass = $method->getDeclaringClass(); + if ( + $declaringClass->isInterface() + && $this->isProvidedByBuiltinAncestor($classReflection, $declaringClass->getName()) + ) { + // A non-abstract built-in class implementing the interface always + // provides its methods at runtime, even when a stub reports one as + // abstract because it is version-gated (e.g. IntlBreakIterator + + // IteratorAggregate::getIterator(), SimpleXMLElement + RecursiveIterator). + continue; + } + $classLikeDescription = 'Non-abstract class'; if ($classReflection->isEnum()) { $classLikeDescription = 'Enum'; @@ -65,4 +77,21 @@ public function processNode(Node $node, Scope $scope): array return $messages; } + private function isProvidedByBuiltinAncestor(ClassReflection $classReflection, string $interfaceName): bool + { + foreach ($classReflection->getParents() as $parent) { + if (!$parent->isBuiltin()) { + continue; + } + if ($parent->isAbstract()) { + continue; + } + if ($parent->implementsInterface($interfaceName)) { + return true; + } + } + + return false; + } + } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRulePhp74Test.php b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRulePhp74Test.php new file mode 100644 index 00000000000..98b87dd5e9c --- /dev/null +++ b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRulePhp74Test.php @@ -0,0 +1,31 @@ + + */ +class MissingMethodImplementationRulePhp74Test extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new MissingMethodImplementationRule(); + } + + public function testBug14964(): void + { + $this->analyse([__DIR__ . '/data/bug-14964.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/data/missing-method-impl-php74.neon', + ]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-14964.php b/tests/PHPStan/Rules/Methods/data/bug-14964.php new file mode 100644 index 00000000000..97e4754ebc9 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14964.php @@ -0,0 +1,11 @@ +