Skip to content

Commit 82a2630

Browse files
Fix IntersectionType::isIterableOnce
1 parent 5878035 commit 82a2630

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

src/Type/IntersectionType.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,10 @@ public function isIterable(): TrinaryLogic
599599

600600
public function isIterableAtLeastOnce(): TrinaryLogic
601601
{
602-
return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce());
602+
return $this->intersectResults(
603+
static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce(),
604+
static fn (Type $type): bool => !$type->isIterable()->no(),
605+
);
603606
}
604607

605608
public function getArraySize(): Type
@@ -1242,17 +1245,29 @@ public function getFiniteTypes(): array
12421245
}
12431246

12441247
/**
1245-
* @param callable(Type $type): TrinaryLogic $getResult
1248+
* @param callable(Type $type): TrinaryLogic $getResult
1249+
* @param (callable(Type $type): TrinaryLogic)|null $filter
12461250
*/
1247-
private function intersectResults(callable $getResult): TrinaryLogic
1248-
{
1249-
return TrinaryLogic::lazyMaxMin($this->types, $getResult);
1251+
private function intersectResults(
1252+
callable $getResult,
1253+
?callable $filter = null,
1254+
): TrinaryLogic {
1255+
$types = $this->types;
1256+
if ($filter !== null) {
1257+
$types = array_filter($types, $filter);
1258+
}
1259+
if (count($types) === 0) {
1260+
return TrinaryLogic::createNo();
1261+
}
1262+
1263+
return TrinaryLogic::lazyMaxMin($types, $getResult);
12501264
}
12511265

12521266
/**
1253-
* @param callable(Type $type): Type $getType
1267+
* @param callable(Type $type): Type $getType
1268+
* @param (callable(Type $type): Type)|null $filter
12541269
*/
1255-
private function intersectTypes(callable $getType): Type
1270+
private function intersectTypes(callable $getType, ?callable $filter = null): Type
12561271
{
12571272
$operands = array_map($getType, $this->types);
12581273
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)