diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 48e9c7e648..8294ae89b4 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -826,25 +826,33 @@ private function getAncestors(): array $this->getName() => $this, ]; + $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void { + if (array_key_exists($name, $ancestors)) { + return; + } + + $ancestors[$name] = $classReflection; + }; + foreach ($this->getInterfaces() as $interface) { - $ancestors[$interface->getName()] = $interface; + $addToAncestors($interface->getName(), $interface); foreach ($interface->getAncestors() as $name => $ancestor) { - $ancestors[$name] = $ancestor; + $addToAncestors($name, $ancestor); } } foreach ($this->getTraits() as $trait) { - $ancestors[$trait->getName()] = $trait; + $addToAncestors($trait->getName(), $trait); foreach ($trait->getAncestors() as $name => $ancestor) { - $ancestors[$name] = $ancestor; + $addToAncestors($name, $ancestor); } } $parent = $this->getParentClass(); if ($parent !== false) { - $ancestors[$parent->getName()] = $parent; + $addToAncestors($parent->getName(), $parent); foreach ($parent->getAncestors() as $name => $ancestor) { - $ancestors[$name] = $ancestor; + $addToAncestors($name, $ancestor); } } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1e3039e44c..1999addd54 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -9773,6 +9773,11 @@ public function dataBug2648(): array return $this->gatherAssertTypes(__DIR__ . '/data/bug-2648.php'); } + public function dataBug2740(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/bug-2740.php'); + } + /** * @dataProvider dataBug2574 * @dataProvider dataBug2577 @@ -9791,6 +9796,7 @@ public function dataBug2648(): array * @dataProvider dataPsalmPrefixedTagsWithUnresolvableTypes * @dataProvider dataComplexGenericsExample * @dataProvider dataBug2648 + * @dataProvider dataBug2740 * @param ConstantStringType $expectedType * @param Type $actualType */ diff --git a/tests/PHPStan/Analyser/data/bug-2740.php b/tests/PHPStan/Analyser/data/bug-2740.php new file mode 100644 index 0000000000..a5cdb70781 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-2740.php @@ -0,0 +1,12 @@ +analyse([__DIR__ . '/data/bug2740.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug2740.php b/tests/PHPStan/Rules/Methods/data/bug2740.php new file mode 100644 index 0000000000..731a62d0ed --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug2740.php @@ -0,0 +1,66 @@ + + */ +interface Collection extends \IteratorAggregate +{ +} + +/** + * A member of a collection. Also a collection containing only itself. + * + * In the real world, this would contain additional methods. + */ +interface Member extends Collection +{ + +} + +class MemberImpl implements Member +{ + + /** + * @return \Iterator + */ + public function getIterator(): \Iterator + { + return new \ArrayIterator([$this]); + } + +} + +class CollectionImpl implements Collection +{ + + /** + * @var array + */ + private $members; + + public function __construct(Member ...$members) + { + $this->members = $members; + } + + /** + * @return Member + */ + public function getMember(): Member + { + return new MemberImpl(); + } + + /** + * @return \Iterator + */ + public function getIterator(): \Iterator + { + return new \ArrayIterator($this->members); + } + +}