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' => '