Skip to content

Commit 6ead4dd

Browse files
Describe Traversable
1 parent 78ac6f2 commit 6ead4dd

File tree

4 files changed

+70
-30
lines changed

4 files changed

+70
-30
lines changed

src/Type/ObjectType.php

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55
use ArrayAccess;
66
use Closure;
77
use Countable;
8-
use DateTime;
9-
use DateTimeImmutable;
10-
use DateTimeInterface;
11-
use Error;
12-
use Exception;
138
use Iterator;
149
use IteratorAggregate;
1510
use PHPStan\Analyser\OutOfClassScope;
@@ -1560,23 +1555,16 @@ private function getInterfaces(): array
15601555

15611556
public function tryRemove(Type $typeToRemove): ?Type
15621557
{
1563-
if ($this->getClassName() === DateTimeInterface::class) {
1564-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) {
1565-
return new ObjectType(DateTime::class);
1566-
}
1567-
1568-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) {
1569-
return new ObjectType(DateTimeImmutable::class);
1570-
}
1571-
}
1572-
1573-
if ($this->getClassName() === Throwable::class) {
1574-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Error::class) {
1575-
return new ObjectType(Exception::class); // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1576-
}
1577-
1578-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Exception::class) { // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1579-
return new ObjectType(Error::class);
1558+
foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) {
1559+
if ($this->getClassName() === $baseClass && $typeToRemove instanceof ObjectType) {
1560+
foreach ($classes as $index => $class) {
1561+
if ($typeToRemove->getClassName() === $class) {
1562+
unset($classes[$index]);
1563+
return TypeCombinator::union(
1564+
...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes)
1565+
);
1566+
}
1567+
}
15801568
}
15811569
}
15821570

src/Type/UnionType.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
use DateTime;
66
use DateTimeImmutable;
77
use DateTimeInterface;
8+
use Error;
9+
use Exception;
10+
use Iterator;
11+
use IteratorAggregate;
812
use PHPStan\Php\PhpVersion;
913
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
1014
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
@@ -26,6 +30,8 @@
2630
use PHPStan\Type\Generic\TemplateTypeVariance;
2731
use PHPStan\Type\Generic\TemplateUnionType;
2832
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
33+
use Throwable;
34+
use Traversable;
2935
use function array_diff_assoc;
3036
use function array_fill_keys;
3137
use function array_map;
@@ -45,6 +51,12 @@ class UnionType implements CompoundType
4551

4652
use NonGeneralizableTypeTrait;
4753

54+
public const EQUAL_UNION_CLASSES = [
55+
DateTimeInterface::class => [DateTimeImmutable::class, DateTime::class],
56+
Throwable::class => [Error::class, Exception::class],
57+
Traversable::class => [IteratorAggregate::class, Iterator::class],
58+
];
59+
4860
private bool $sortedTypes = false;
4961

5062
/** @var array<int, string> */
@@ -183,14 +195,15 @@ public function getConstantStrings(): array
183195

184196
public function accepts(Type $type, bool $strictTypes): AcceptsResult
185197
{
186-
if (
187-
$type->equals(new ObjectType(DateTimeInterface::class))
188-
&& $this->accepts(
189-
new UnionType([new ObjectType(DateTime::class), new ObjectType(DateTimeImmutable::class)]),
190-
$strictTypes,
191-
)->yes()
192-
) {
193-
return AcceptsResult::createYes();
198+
foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) {
199+
if ($type->equals(new ObjectType($baseClass))) {
200+
$union = TypeCombinator::union(
201+
...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes)
202+
);
203+
if ($this->accepts($union, $strictTypes)->yes()) {
204+
return AcceptsResult::createYes();
205+
}
206+
}
194207
}
195208

196209
$result = AcceptsResult::createNo();

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,4 +1096,9 @@ public function testBug11857(): void
10961096
$this->analyse([__DIR__ . '/data/bug-11857-builder.php'], []);
10971097
}
10981098

1099+
public function testBug12102(): void
1100+
{
1101+
$this->analyse([__DIR__ . '/data/bug-12102.php'], []);
1102+
}
1103+
10991104
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12102;
4+
5+
use Iterator;
6+
use IteratorAggregate;
7+
use Traversable;
8+
9+
class HelloWorld
10+
{
11+
/** @param Traversable<mixed, mixed> $traversable */
12+
public function sayHello(Traversable $traversable): ?Iterator
13+
{
14+
if (!$traversable instanceof IteratorAggregate) {
15+
return $traversable;
16+
}
17+
18+
return null;
19+
}
20+
21+
/** @param iterable<mixed, mixed> $iterable */
22+
public function sayHello2(iterable $iterable): ?Iterator
23+
{
24+
if (\is_array($iterable)) {
25+
return null;
26+
}
27+
28+
if (!$iterable instanceof IteratorAggregate) {
29+
return $iterable;
30+
}
31+
32+
return null;
33+
}
34+
}

0 commit comments

Comments
 (0)