Skip to content

Commit 0411ad9

Browse files
Fix IntersectionType::isIterableOnce
1 parent 5878035 commit 0411ad9

File tree

3 files changed

+65
-6
lines changed

3 files changed

+65
-6
lines changed

src/Type/IntersectionType.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use PHPStan\Type\Generic\TemplateTypeVariance;
4040
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
4141
use PHPStan\Type\Traits\NonRemoveableTypeTrait;
42+
use function array_filter;
4243
use function array_intersect_key;
4344
use function array_map;
4445
use function array_shift;
@@ -599,7 +600,10 @@ public function isIterable(): TrinaryLogic
599600

600601
public function isIterableAtLeastOnce(): TrinaryLogic
601602
{
602-
return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce());
603+
return $this->intersectResults(
604+
static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce(),
605+
static fn (Type $type): bool => !$type->isIterable()->no(),
606+
);
603607
}
604608

605609
public function getArraySize(): Type
@@ -1243,16 +1247,29 @@ public function getFiniteTypes(): array
12431247

12441248
/**
12451249
* @param callable(Type $type): TrinaryLogic $getResult
1250+
* @param (callable(Type $type): bool)|null $filter
12461251
*/
1247-
private function intersectResults(callable $getResult): TrinaryLogic
1248-
{
1249-
return TrinaryLogic::lazyMaxMin($this->types, $getResult);
1252+
private function intersectResults(
1253+
callable $getResult,
1254+
?callable $filter = null,
1255+
): TrinaryLogic
1256+
{
1257+
$types = $this->types;
1258+
if ($filter !== null) {
1259+
$types = array_filter($types, $filter);
1260+
}
1261+
if (count($types) === 0) {
1262+
return TrinaryLogic::createNo();
1263+
}
1264+
1265+
return TrinaryLogic::lazyMaxMin($types, $getResult);
12501266
}
12511267

12521268
/**
1253-
* @param callable(Type $type): Type $getType
1269+
* @param callable(Type $type): Type $getType
1270+
* @param (callable(Type $type): Type)|null $filter
12541271
*/
1255-
private function intersectTypes(callable $getType): Type
1272+
private function intersectTypes(callable $getType, ?callable $filter = null): Type
12561273
{
12571274
$operands = array_map($getType, $this->types);
12581275
return TypeCombinator::intersect(...$operands);

tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ public function testBug8292(): void
4040
$this->analyse([__DIR__ . '/data/bug-8292.php'], []);
4141
}
4242

43+
public function testBug13248(): void
44+
{
45+
$this->analyse([__DIR__ . '/data/bug-13248.php'], []);
46+
}
47+
4348
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Bug13248;
4+
5+
use ArrayIterator;
6+
use IteratorAggregate;
7+
use Traversable;
8+
9+
class X
10+
{
11+
}
12+
13+
/**
14+
* @implements IteratorAggregate<int, string>
15+
*/
16+
class Y extends X implements IteratorAggregate
17+
{
18+
/**
19+
* @return ArrayIterator<int<0, 2>, 'a'|'b'|'c'>
20+
*/
21+
public function getIterator(): Traversable
22+
{
23+
return new ArrayIterator(['a', 'b', 'c']);
24+
}
25+
}
26+
27+
/**
28+
* @return X&Traversable<int, string>
29+
*/
30+
function y(): X
31+
{
32+
return new Y();
33+
}
34+
35+
foreach (y() as $item) { // hm?
36+
echo $item . PHP_EOL;
37+
}

0 commit comments

Comments
 (0)