diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 735093eb6..f154dc936 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -388,7 +388,6 @@ getClassTemplateTypes - has diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php index 5e9d9e5a9..9b8c82617 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php @@ -49,10 +49,14 @@ class ClassLikeStorageProvider return self::$storage[$fq_classlike_name_lc]; } + /** + * @psalm-mutation-free + */ public function has(string $fq_classlike_name): bool { $fq_classlike_name_lc = strtolower($fq_classlike_name); + /** @psalm-suppress ImpureStaticProperty Used only for caching */ return isset(self::$storage[$fq_classlike_name_lc]); } diff --git a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php index e5a85ddda..a2321886d 100644 --- a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php @@ -33,7 +33,6 @@ use Psalm\Type\Atomic\TTemplateParamClass; use Psalm\Type\Atomic\TTemplatePropertiesOf; use Psalm\Type\Atomic\TTemplateValueOf; use Psalm\Type\Union; -use Throwable; use function array_fill; use function array_keys; @@ -1233,13 +1232,13 @@ class TemplateStandinTypeReplacer $input_type_params = []; } - try { - $input_class_storage = $codebase->classlike_storage_provider->get($input_type_part->value); - $container_class_storage = $codebase->classlike_storage_provider->get($container_type_part->value); - $container_type_params_covariant = $container_class_storage->template_covariants; - } catch (Throwable $e) { - $input_class_storage = null; - } + $input_class_storage = $codebase->classlike_storage_provider->has($input_type_part->value) + ? $codebase->classlike_storage_provider->get($input_type_part->value) + : null; + + $container_type_params_covariant = $codebase->classlike_storage_provider->has($container_type_part->value) + ? $codebase->classlike_storage_provider->get($container_type_part->value)->template_covariants + : null; if ($input_type_part->value !== $container_type_part->value && $input_class_storage @@ -1266,8 +1265,12 @@ class TemplateStandinTypeReplacer $template_extends = $input_class_storage->template_extended_params; - if (isset($template_extends[$container_type_part->value])) { - $params = $template_extends[$container_type_part->value]; + $container_type_part_value = $container_type_part->value === 'iterable' + ? 'Traversable' + : $container_type_part->value; + + if (isset($template_extends[$container_type_part_value])) { + $params = $template_extends[$container_type_part_value]; $new_input_params = []; diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 7905ac337..8442696e6 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -46,6 +46,40 @@ class FunctionCallTest extends TestCase '$genericList===' => 'list{1, 2, 3}', ], ], + 'inferIterableFromTraversable' => [ + 'code' => ' + */ + function getStrings(): SplFixedArray + { + return SplFixedArray::fromArray(["fst", "snd", "thr"]); + } + /** + * @return SplFixedArray + */ + function getIntegers(): SplFixedArray + { + return SplFixedArray::fromArray([1, 2, 3]); + } + /** + * @template K + * @template A + * @template B + * @param iterable $lhs + * @param iterable $rhs + * @return iterable + */ + function mergeIterable(iterable $lhs, iterable $rhs): iterable + { + foreach ($lhs as $k => $v) { yield $k => $v; } + foreach ($rhs as $k => $v) { yield $k => $v; } + } + $iterable = mergeIterable(getStrings(), getIntegers());', + 'assertions' => [ + '$iterable===' => 'iterable', + ], + ], 'countShapedArrays' => [ 'code' => '