Skip to content

Commit a3fb8e9

Browse files
Fix phpstan/phpstan#13247: iterable is equal to array|Traversable (#5144)
Co-authored-by: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com> Co-authored-by: Vincent Langlet <vincentlanglet@hotmail.fr>
1 parent 869ddb3 commit a3fb8e9

File tree

5 files changed

+121
-8
lines changed

5 files changed

+121
-8
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ parameters:
17881788
-
17891789
rawMessage: 'Doing instanceof PHPStan\Type\IterableType is error-prone and deprecated. Use Type::isIterable() instead.'
17901790
identifier: phpstanApi.instanceofType
1791-
count: 1
1791+
count: 3
17921792
path: src/Type/UnionType.php
17931793

17941794
-

src/Type/IterableType.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,7 @@ private function isNestedTypeSuperTypeOf(Type $a, Type $b): IsSuperTypeOfResult
136136
public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
137137
{
138138
if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) {
139-
return $otherType->isSuperTypeOf(new UnionType([
140-
new ArrayType($this->keyType, $this->itemType),
141-
new IntersectionType([
142-
new ObjectType(Traversable::class),
143-
$this,
144-
]),
145-
]));
139+
return $otherType->isSuperTypeOf($this->toArrayOrTraversable());
146140
}
147141

148142
if ($otherType instanceof self) {
@@ -231,6 +225,17 @@ public function toArray(): Type
231225
return new ArrayType($this->keyType, $this->getItemType());
232226
}
233227

228+
public function toArrayOrTraversable(): Type
229+
{
230+
return new UnionType([
231+
new ArrayType($this->keyType, $this->itemType),
232+
new GenericObjectType(Traversable::class, [
233+
$this->keyType,
234+
$this->itemType,
235+
]),
236+
]);
237+
}
238+
234239
public function toArrayKey(): Type
235240
{
236241
return new ErrorType();

src/Type/UnionType.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ public function getConstantStrings(): array
194194

195195
public function accepts(Type $type, bool $strictTypes): AcceptsResult
196196
{
197+
if ($type instanceof IterableType) {
198+
return $this->accepts($type->toArrayOrTraversable(), $strictTypes);
199+
}
200+
197201
foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) {
198202
if (!$type->equals(new ObjectType($baseClass))) {
199203
continue;
@@ -1073,6 +1077,10 @@ public function toCoercedArgumentType(bool $strictTypes): Type
10731077

10741078
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
10751079
{
1080+
if ($receivedType instanceof IterableType) {
1081+
$receivedType = $receivedType->toArrayOrTraversable();
1082+
}
1083+
10761084
$types = TemplateTypeMap::createEmpty();
10771085
if ($receivedType instanceof UnionType) {
10781086
$myTypes = [];

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2741,4 +2741,9 @@ public function testBug12363(): void
27412741
$this->analyse([__DIR__ . '/data/bug-12363.php'], []);
27422742
}
27432743

2744+
public function testBug13247(): void
2745+
{
2746+
$this->analyse([__DIR__ . '/data/bug-13247.php'], []);
2747+
}
2748+
27442749
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug13247;
6+
7+
use Traversable;
8+
9+
/**
10+
* @template K as array-key
11+
* @template V
12+
*
13+
* @param array<K, V>|Traversable<K, V> $input
14+
*
15+
* @return array<K, V>
16+
*/
17+
function as_array(array|Traversable $input): array {
18+
return iter_as_array($input);
19+
}
20+
21+
/**
22+
* @template K as array-key
23+
* @template V
24+
*
25+
* @param iterable<K, V> $input
26+
*
27+
* @return array<K, V>
28+
*/
29+
function iter_as_array(iterable $input): array {
30+
return as_array($input);
31+
}
32+
33+
/**
34+
* @param array|Traversable $input
35+
*
36+
* @return array
37+
*/
38+
function as_array2(array|Traversable $input): array {
39+
return iter_as_array2($input);
40+
}
41+
42+
/**
43+
* @param iterable $input
44+
*
45+
* @return array
46+
*/
47+
function iter_as_array2(iterable $input): array {
48+
return as_array2($input);
49+
}
50+
51+
/**
52+
* @param array<int, int>|Traversable<int, int> $input
53+
*
54+
* @return array<int, int>
55+
*/
56+
function as_array3(array|Traversable $input): array {
57+
return iter_as_array3($input);
58+
}
59+
60+
/**
61+
* @param iterable<int, int> $input
62+
*
63+
* @return array<int, int>
64+
*/
65+
function iter_as_array3(iterable $input): array {
66+
return as_array3($input);
67+
}
68+
69+
/**
70+
* @phpstan-template T of iterable<int, int>
71+
*
72+
* @param T $input
73+
*
74+
* @return mixed
75+
*/
76+
function test1(iterable $input) {
77+
test2($input);
78+
iter_as_array($input);
79+
80+
return as_array($input);
81+
}
82+
83+
/**
84+
* @phpstan-template U of Traversable<int, int>|array<int, int>
85+
*
86+
* @param U $input
87+
*
88+
* @return mixed
89+
*/
90+
function test2($input) {
91+
test1($input);
92+
iter_as_array($input);
93+
94+
return as_array($input);
95+
}

0 commit comments

Comments
 (0)