diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3b050733d9f..d6ddf9974a5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -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 - diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 46e004ae235..2cf46b754e9 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -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) { - 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) { @@ -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(); diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 208e5e3b43a..1348e27dc5f 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -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; @@ -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 = []; diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index d06950ad68f..6bb50169fe6 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -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'], []); + } + } 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..c872f2e1be4 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-13247.php @@ -0,0 +1,95 @@ += 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 { + return iter_as_array($input); +} + +/** + * @template K as array-key + * @template V + * + * @param iterable $input + * + * @return array + */ +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|Traversable $input + * + * @return array + */ +function as_array3(array|Traversable $input): array { + return iter_as_array3($input); +} + +/** + * @param iterable $input + * + * @return array + */ +function iter_as_array3(iterable $input): array { + return as_array3($input); +} + +/** + * @phpstan-template T of iterable + * + * @param T $input + * + * @return mixed + */ +function test1(iterable $input) { + test2($input); + iter_as_array($input); + + return as_array($input); +} + +/** + * @phpstan-template U of Traversable|array + * + * @param U $input + * + * @return mixed + */ +function test2($input) { + test1($input); + iter_as_array($input); + + return as_array($input); +}