diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index e276d7c0ef..49a95678e4 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -696,7 +696,8 @@ public function getIterableKeyType(): Type } } - if ($this->isInstanceOf(Traversable::class)->yes()) { + $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes(); + if ($this->isInstanceOf(Traversable::class)->yes() && !$extraOffsetAccessible) { $isTraversable = true; $tKey = GenericTypeVariableResolver::getType($this, Traversable::class, 'TKey'); if ($tKey !== null) { @@ -712,6 +713,10 @@ public function getIterableKeyType(): Type )->getReturnType()); } + if ($extraOffsetAccessible) { + return new MixedType(true); + } + if ($isTraversable) { return new MixedType(); } @@ -732,7 +737,8 @@ public function getIterableValueType(): Type } } - if ($this->isInstanceOf(Traversable::class)->yes()) { + $extraOffsetAccessible = $this->isExtraOffsetAccessibleClass()->yes(); + if ($this->isInstanceOf(Traversable::class)->yes() && !$extraOffsetAccessible) { $isTraversable = true; $tValue = GenericTypeVariableResolver::getType($this, Traversable::class, 'TValue'); if ($tValue !== null) { @@ -748,6 +754,10 @@ public function getIterableValueType(): Type )->getReturnType()); } + if ($extraOffsetAccessible) { + return new MixedType(true); + } + if ($isTraversable) { return new MixedType(); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 11b38874e2..fa433e9cc7 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -821,6 +821,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-search-type-specifying.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-replace.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6889.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/simplexml.php'); } /** diff --git a/tests/PHPStan/Analyser/data/simplexml.php b/tests/PHPStan/Analyser/data/simplexml.php new file mode 100644 index 0000000000..4bae93d15b --- /dev/null +++ b/tests/PHPStan/Analyser/data/simplexml.php @@ -0,0 +1,69 @@ +item); + foreach ($data->item as $item) { + assertType('SimpleXMLElement', $item); + assertType('SimpleXMLElement|null', $item['name']); + } + } + +} + +class Bar extends SimpleXMLElement +{ + + public function getAddressByGps() + { + /** @var self|null $data */ + $data = doFoo(); + + if ($data === null) { + return; + } + + assertType('(SimpleXMLIteratorBug\Bar|null)', $data->item); + foreach ($data->item as $item) { + assertType(self::class, $item); + assertType('SimpleXMLIteratorBug\Bar|null', $item['name']); + } + } + +} + +class Baz +{ + + public function getAddressByGps() + { + /** @var Bar|null $data */ + $data = doFoo(); + + if ($data === null) { + return; + } + + assertType('(SimpleXMLIteratorBug\Bar|null)', $data->item); + foreach ($data->item as $item) { + assertType(Bar::class, $item); + assertType('SimpleXMLIteratorBug\Bar|null', $item['name']); + } + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index 2783431de3..8ff85821c5 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -210,8 +210,6 @@ public function testValueOfEnum(): void $this->markTestSkipped('This test needs PHP 8.1'); } - require __DIR__ . '/data/value-of-enum.php'; - $this->analyse([__DIR__ . '/data/value-of-enum.php'], [ [ 'PHPDoc tag @param for parameter $shouldError with type string is incompatible with native type int.', diff --git a/tests/PHPStan/Rules/PhpDoc/data/value-of-enum.php b/tests/PHPStan/Rules/PhpDoc/data/value-of-enum.php index aa467170c7..a2dc8d0255 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/value-of-enum.php +++ b/tests/PHPStan/Rules/PhpDoc/data/value-of-enum.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ValueOfEnum; +namespace ValueOfEnumPhpdoc; enum Country: string {