diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 22b6c7b0e3..29cc9edb52 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -39,6 +39,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; +use function array_filter; use function array_intersect_key; use function array_map; use function array_shift; @@ -599,7 +600,10 @@ public function isIterable(): TrinaryLogic public function isIterableAtLeastOnce(): TrinaryLogic { - return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce()); + return $this->intersectResults( + static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce(), + static fn (Type $type): bool => !$type->isIterable()->no(), + ); } public function getArraySize(): Type @@ -1243,16 +1247,29 @@ public function getFiniteTypes(): array /** * @param callable(Type $type): TrinaryLogic $getResult + * @param (callable(Type $type): bool)|null $filter */ - private function intersectResults(callable $getResult): TrinaryLogic - { - return TrinaryLogic::lazyMaxMin($this->types, $getResult); + private function intersectResults( + callable $getResult, + ?callable $filter = null, + ): TrinaryLogic + { + $types = $this->types; + if ($filter !== null) { + $types = array_filter($types, $filter); + } + if (count($types) === 0) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::lazyMaxMin($types, $getResult); } /** - * @param callable(Type $type): Type $getType + * @param callable(Type $type): Type $getType + * @param (callable(Type $type): Type)|null $filter */ - private function intersectTypes(callable $getType): Type + private function intersectTypes(callable $getType, ?callable $filter = null): Type { $operands = array_map($getType, $this->types); return TypeCombinator::intersect(...$operands); diff --git a/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php index a99a20557c..d6c1445dfa 100644 --- a/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php @@ -40,4 +40,9 @@ public function testBug8292(): void $this->analyse([__DIR__ . '/data/bug-8292.php'], []); } + public function testBug13248(): void + { + $this->analyse([__DIR__ . '/data/bug-13248.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-13248.php b/tests/PHPStan/Rules/Arrays/data/bug-13248.php new file mode 100644 index 0000000000..7d67f895eb --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-13248.php @@ -0,0 +1,37 @@ + + */ +class Y extends X implements IteratorAggregate +{ + /** + * @return ArrayIterator, 'a'|'b'|'c'> + */ + public function getIterator(): Traversable + { + return new ArrayIterator(['a', 'b', 'c']); + } +} + +/** + * @return X&Traversable + */ +function y(): X +{ + return new Y(); +} + +foreach (y() as $item) { // hm? + echo $item . PHP_EOL; +}