diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d91eb9b78bf..2a5dc9172b9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1776,7 +1776,7 @@ parameters: - rawMessage: 'Doing instanceof PHPStan\Type\IterableType is error-prone and deprecated. Use Type::isIterable() instead.' identifier: phpstanApi.instanceofType - count: 1 + count: 2 path: src/Type/UnionType.php - diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 46e004ae235..dd1162cdbbd 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -138,9 +138,9 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { return $otherType->isSuperTypeOf(new UnionType([ new ArrayType($this->keyType, $this->itemType), - new IntersectionType([ - new ObjectType(Traversable::class), - $this, + new GenericObjectType(Traversable::class, [ + $this->keyType, + $this->itemType, ]), ])); } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 208e5e3b43a..47d25f35f8c 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -25,6 +25,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateIterableType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; @@ -33,6 +34,7 @@ use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use Throwable; +use Traversable; use function array_diff_assoc; use function array_fill_keys; use function array_map; @@ -1073,6 +1075,16 @@ public function toCoercedArgumentType(bool $strictTypes): Type public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { + if ($receivedType instanceof IterableType && !$receivedType instanceof TemplateIterableType) { + return $this->inferTemplateTypes(new UnionType([ + new ArrayType($receivedType->getKeyType(), $receivedType->getItemType()), + new GenericObjectType(Traversable::class, [ + $receivedType->getKeyType(), + $receivedType->getItemType(), + ]), + ])); + } + $types = TemplateTypeMap::createEmpty(); if ($receivedType instanceof UnionType) { $myTypes = []; diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 77ef9843bfe..ba544ae9d35 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2735,4 +2735,9 @@ public function testBug10559(): void $this->analyse([__DIR__ . '/data/bug-10559.php'], []); } + public function testBug13247(): void + { + $this->analyse([__DIR__ . '/data/bug-13247.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-13247.php b/tests/PHPStan/Rules/Functions/data/bug-13247.php new file mode 100644 index 00000000000..5acd49e8793 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-13247.php @@ -0,0 +1,35 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug13247; + +use Traversable; + +/** + * @template K as array-key + * @template V + * + * @param array|Traversable $input + * + * @return array + */ +function as_array(array|Traversable $input): array { + if ($input instanceof Traversable) { + return iterator_to_array($input); + } + + return $input; +} + +/** + * @template K as array-key + * @template V + * + * @param iterable $input + * + * @return array + */ +function iter_as_array(iterable $input): array { + return as_array($input); +}