Skip to content

Commit 5cd7a41

Browse files
Describe Traversable
1 parent 01bf65c commit 5cd7a41

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;
@@ -1588,23 +1583,16 @@ private function getInterfaces(): array
15881583

15891584
public function tryRemove(Type $typeToRemove): ?Type
15901585
{
1591-
if ($this->getClassName() === DateTimeInterface::class) {
1592-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) {
1593-
return new ObjectType(DateTime::class);
1594-
}
1595-
1596-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) {
1597-
return new ObjectType(DateTimeImmutable::class);
1598-
}
1599-
}
1600-
1601-
if ($this->getClassName() === Throwable::class) {
1602-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Error::class) {
1603-
return new ObjectType(Exception::class); // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1604-
}
1605-
1606-
if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Exception::class) { // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
1607-
return new ObjectType(Error::class);
1586+
foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) {
1587+
if ($this->getClassName() === $baseClass && $typeToRemove instanceof ObjectType) {
1588+
foreach ($classes as $index => $class) {
1589+
if ($typeToRemove->getClassName() === $class) {
1590+
unset($classes[$index]);
1591+
return TypeCombinator::union(
1592+
...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes)
1593+
);
1594+
}
1595+
}
16081596
}
16091597
}
16101598

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> */
@@ -191,14 +203,15 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic
191203

192204
public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
193205
{
194-
if (
195-
$type->equals(new ObjectType(DateTimeInterface::class))
196-
&& $this->accepts(
197-
new UnionType([new ObjectType(DateTime::class), new ObjectType(DateTimeImmutable::class)]),
198-
$strictTypes,
199-
)->yes()
200-
) {
201-
return AcceptsResult::createYes();
206+
foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) {
207+
if ($type->equals(new ObjectType($baseClass))) {
208+
$union = TypeCombinator::union(
209+
...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes)
210+
);
211+
if ($this->accepts($union, $strictTypes)->yes()) {
212+
return AcceptsResult::createYes();
213+
}
214+
}
202215
}
203216

204217
$result = AcceptsResult::createNo();

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

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

1067+
public function testBug12102(): void
1068+
{
1069+
$this->analyse([__DIR__ . '/data/bug-12102.php'], []);
1070+
}
1071+
10671072
}
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)