diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d91eb9b78bf..a7df6cbabe5 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: 3 path: src/Type/UnionType.php - diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 208e5e3b43a..6fee6d9fdac 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; @@ -194,6 +196,16 @@ public function getConstantStrings(): array public function accepts(Type $type, bool $strictTypes): AcceptsResult { + if ($type instanceof IterableType && !$type instanceof TemplateIterableType) { + return $this->accepts(new UnionType([ + new ArrayType($type->getIterableKeyType(), $type->getIterableValueType()), + new GenericObjectType(Traversable::class, [ + $type->getIterableKeyType(), + $type->getIterableValueType(), + ]), + ]), $strictTypes); + } + foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) { if (!$type->equals(new ObjectType($baseClass))) { continue; @@ -1073,6 +1085,16 @@ public function toCoercedArgumentType(bool $strictTypes): Type public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { + if ($receivedType instanceof IterableType && !$receivedType instanceof TemplateIterableType) { + $receivedType = new UnionType([ + new ArrayType($receivedType->getIterableKeyType(), $receivedType->getIterableValueType()), + new GenericObjectType(Traversable::class, [ + $receivedType->getIterableKeyType(), + $receivedType->getIterableValueType(), + ]), + ]); + } + $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); +}