Skip to content

Commit b7aa7ea

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix iterable<K,V> not accepted as array<K,V>|Traversable<K,V>
- Fixed IterableType::isSubTypeOf to decompose iterable<K,V> into array<K,V>|GenericObjectType(Traversable<K,V>) instead of array<K,V>|(ObjectType(Traversable)&iterable<K,V>), preserving generic type information for proper subtype checking - Fixed UnionType::inferTemplateTypes to decompose IterableType into array|Traversable union before template inference, allowing template types to be correctly resolved from iterable arguments - Added regression test for phpstan/phpstan#13247 Closes phpstan/phpstan#13247
1 parent 1b1f48c commit b7aa7ea

File tree

5 files changed

+56
-4
lines changed

5 files changed

+56
-4
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ parameters:
17761776
-
17771777
rawMessage: 'Doing instanceof PHPStan\Type\IterableType is error-prone and deprecated. Use Type::isIterable() instead.'
17781778
identifier: phpstanApi.instanceofType
1779-
count: 1
1779+
count: 2
17801780
path: src/Type/UnionType.php
17811781

17821782
-

src/Type/IterableType.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
138138
if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) {
139139
return $otherType->isSuperTypeOf(new UnionType([
140140
new ArrayType($this->keyType, $this->itemType),
141-
new IntersectionType([
142-
new ObjectType(Traversable::class),
143-
$this,
141+
new GenericObjectType(Traversable::class, [
142+
$this->keyType,
143+
$this->itemType,
144144
]),
145145
]));
146146
}

src/Type/UnionType.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use PHPStan\TrinaryLogic;
2626
use PHPStan\Type\Enum\EnumCaseObjectType;
2727
use PHPStan\Type\Generic\GenericClassStringType;
28+
use PHPStan\Type\Generic\GenericObjectType;
2829
use PHPStan\Type\Generic\TemplateIterableType;
2930
use PHPStan\Type\Generic\TemplateMixedType;
3031
use PHPStan\Type\Generic\TemplateType;
@@ -33,6 +34,7 @@
3334
use PHPStan\Type\Generic\TemplateUnionType;
3435
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
3536
use Throwable;
37+
use Traversable;
3638
use function array_diff_assoc;
3739
use function array_fill_keys;
3840
use function array_map;
@@ -1073,6 +1075,16 @@ public function toCoercedArgumentType(bool $strictTypes): Type
10731075

10741076
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
10751077
{
1078+
if ($receivedType instanceof IterableType && !$receivedType instanceof TemplateIterableType) {
1079+
return $this->inferTemplateTypes(new UnionType([
1080+
new ArrayType($receivedType->getKeyType(), $receivedType->getItemType()),
1081+
new GenericObjectType(Traversable::class, [
1082+
$receivedType->getKeyType(),
1083+
$receivedType->getItemType(),
1084+
]),
1085+
]));
1086+
}
1087+
10761088
$types = TemplateTypeMap::createEmpty();
10771089
if ($receivedType instanceof UnionType) {
10781090
$myTypes = [];

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,4 +2735,9 @@ public function testBug10559(): void
27352735
$this->analyse([__DIR__ . '/data/bug-10559.php'], []);
27362736
}
27372737

2738+
public function testBug13247(): void
2739+
{
2740+
$this->analyse([__DIR__ . '/data/bug-13247.php'], []);
2741+
}
2742+
27382743
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
if ($input instanceof Traversable) {
19+
return iterator_to_array($input);
20+
}
21+
22+
return $input;
23+
}
24+
25+
/**
26+
* @template K as array-key
27+
* @template V
28+
*
29+
* @param iterable<K, V> $input
30+
*
31+
* @return array<K, V>
32+
*/
33+
function iter_as_array(iterable $input): array {
34+
return as_array($input);
35+
}

0 commit comments

Comments
 (0)