Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,7 @@ parameters:
-
rawMessage: 'Doing instanceof PHPStan\Type\IterableType is error-prone and deprecated. Use Type::isIterable() instead.'
identifier: phpstanApi.instanceofType
count: 1
count: 3
path: src/Type/UnionType.php

-
Expand Down
19 changes: 12 additions & 7 deletions src/Type/IterableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,7 @@ private function isNestedTypeSuperTypeOf(Type $a, Type $b): IsSuperTypeOfResult
public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
{
if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) {
Copy link
Copy Markdown
Contributor

@staabm staabm Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this case here is guarded also on IntersectionType. does it mean we need a similar fix you do on UnionType->accepts() also in IntersectionType->accepts()?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added by the bot as a fix in a95e598 in order to fix

Parameter #1 $input of function Bug13247\as_array expects array<int, int>|Traversable<int, int>, iterable<int, int> given.

I don't have in mind something where an IntersectionType make sens ; but feel free to provide some case I'll add to my tests.

return $otherType->isSuperTypeOf(new UnionType([
new ArrayType($this->keyType, $this->itemType),
new IntersectionType([
new ObjectType(Traversable::class),
$this,
]),
]));
return $otherType->isSuperTypeOf($this->toArrayOrTraversable());
}

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

public function toArrayOrTraversable(): Type
{
return new UnionType([
new ArrayType($this->keyType, $this->itemType),
new GenericObjectType(Traversable::class, [
$this->keyType,
$this->itemType,
]),
]);
}

public function toArrayKey(): Type
{
return new ErrorType();
Expand Down
8 changes: 8 additions & 0 deletions src/Type/UnionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ public function getConstantStrings(): array

public function accepts(Type $type, bool $strictTypes): AcceptsResult
{
if ($type instanceof IterableType) {
return $this->accepts($type->toArrayOrTraversable(), $strictTypes);
}

foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) {
if (!$type->equals(new ObjectType($baseClass))) {
continue;
Expand Down Expand Up @@ -1073,6 +1077,10 @@ public function toCoercedArgumentType(bool $strictTypes): Type

public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
{
if ($receivedType instanceof IterableType) {
$receivedType = $receivedType->toArrayOrTraversable();
}

$types = TemplateTypeMap::createEmpty();
if ($receivedType instanceof UnionType) {
$myTypes = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2741,4 +2741,9 @@ public function testBug12363(): void
$this->analyse([__DIR__ . '/data/bug-12363.php'], []);
}

public function testBug13247(): void
{
$this->analyse([__DIR__ . '/data/bug-13247.php'], []);
}

}
95 changes: 95 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-13247.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug13247;

use Traversable;

/**
* @template K as array-key
* @template V
*
* @param array<K, V>|Traversable<K, V> $input
*
* @return array<K, V>
*/
function as_array(array|Traversable $input): array {
return iter_as_array($input);
}

/**
* @template K as array-key
* @template V
*
* @param iterable<K, V> $input
*
* @return array<K, V>
*/
function iter_as_array(iterable $input): array {
return as_array($input);
}

/**
* @param array|Traversable $input
*
* @return array
*/
function as_array2(array|Traversable $input): array {
return iter_as_array2($input);
}

/**
* @param iterable $input
*
* @return array
*/
function iter_as_array2(iterable $input): array {
return as_array2($input);
}

/**
* @param array<int, int>|Traversable<int, int> $input
*
* @return array<int, int>
*/
function as_array3(array|Traversable $input): array {
return iter_as_array3($input);
}

/**
* @param iterable<int, int> $input
*
* @return array<int, int>
*/
function iter_as_array3(iterable $input): array {
return as_array3($input);
}

/**
* @phpstan-template T of iterable<int, int>
*
* @param T $input
*
* @return mixed
*/
function test1(iterable $input) {
test2($input);
iter_as_array($input);

return as_array($input);
}

/**
* @phpstan-template U of Traversable<int, int>|array<int, int>
*
* @param U $input
*
* @return mixed
*/
function test2($input) {
test1($input);
iter_as_array($input);

return as_array($input);
}
Loading