diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php index 1afcebc20..03806773c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -577,25 +577,65 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name); - $class_storage = $codebase->classlike_storage_provider->get($fq_class_name); + $class_storage = $codebase->methods->getClassLikeStorageForMethod($method_id); + $calling_class_storage = $codebase->classlike_storage_provider->get($fq_class_name); if ($class_storage->template_types) { $class_template_params = []; if ($lhs_type_part instanceof TGenericObject) { - $reversed_class_template_types = array_reverse(array_keys($class_storage->template_types)); + if ($calling_class_storage->template_types) { + $i = 0; + foreach ($calling_class_storage->template_types as $type_name => $_) { + if (isset($lhs_type_part->type_params[$i])) { + $class_template_params[$type_name] = [ + $lhs_type_part->type_params[$i], + $calling_class_storage->name, + ]; + } - $provided_type_param_count = count($lhs_type_part->type_params); + $i++; + } + } - foreach ($reversed_class_template_types as $i => $type_name) { - if (isset($lhs_type_part->type_params[$provided_type_param_count - 1 - $i])) { - $class_template_params[$type_name] = [ - $lhs_type_part->type_params[$provided_type_param_count - 1 - $i], - $fq_class_name, - ]; - } else { + $e = $calling_class_storage->template_type_extends; + + $i = 0; + foreach ($class_storage->template_types as $type_name => $_) { + if (isset($class_template_params[$type_name])) { + $i++; + continue; + } + + if ($class_storage !== $calling_class_storage + && isset($e[strtolower($class_storage->name)][$type_name]) + ) { + $type_extends = $e[strtolower($class_storage->name)][$type_name]; + + if ($type_extends instanceof Type\Atomic\TGenericParam) { + if (isset($calling_class_storage->template_types[$type_extends->param_name])) { + $mapped_offset = array_search( + $type_extends->param_name, + array_keys($calling_class_storage->template_types) + ); + $class_template_params[$type_name] = [ + $lhs_type_part->type_params[(int) $mapped_offset], + $class_storage->name, + ]; + } + } else { + $class_template_params[$type_name] = [ + new Type\Union([$type_extends]), + $class_storage->name, + ]; + } + } + + if (!isset($class_template_params[$type_name])) { $class_template_params[$type_name] = [Type::getMixed(), null]; } + + $i++; } } else { foreach ($class_storage->template_types as $type_name => $_) { diff --git a/src/Psalm/Internal/Codebase/Methods.php b/src/Psalm/Internal/Codebase/Methods.php index eb2dc723f..0d33a88b2 100644 --- a/src/Psalm/Internal/Codebase/Methods.php +++ b/src/Psalm/Internal/Codebase/Methods.php @@ -6,6 +6,7 @@ use Psalm\Internal\Analyzer\MethodAnalyzer; use Psalm\CodeLocation; use Psalm\Internal\Provider\ClassLikeStorageProvider; use Psalm\Internal\Provider\FileReferenceProvider; +use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\FunctionLikeParameter; use Psalm\Storage\MethodStorage; use Psalm\Type; @@ -595,6 +596,28 @@ class Methods return $storage; } + /** + * @param string $method_id + * + * @return ClassLikeStorage + */ + public function getClassLikeStorageForMethod($method_id) + { + $declaring_method_id = $this->getDeclaringMethodId($method_id); + + if (!$declaring_method_id) { + if (CallMap::inCallMap($method_id)) { + $declaring_method_id = $method_id; + } else { + throw new \UnexpectedValueException('$storage should not be null for ' . $method_id); + } + } + + list($declaring_fq_class_name) = explode('::', $declaring_method_id); + + return $this->classlike_storage_provider->get($declaring_fq_class_name); + } + /** * @param string $method_id * diff --git a/src/Psalm/Internal/Stubs/CoreGenericClasses.php b/src/Psalm/Internal/Stubs/CoreGenericClasses.php index ff5b7dc6e..a565f7449 100644 --- a/src/Psalm/Internal/Stubs/CoreGenericClasses.php +++ b/src/Psalm/Internal/Stubs/CoreGenericClasses.php @@ -451,6 +451,7 @@ class ArrayObject implements IteratorAggregate, Traversable, ArrayAccess, Serial * over Arrays and Objects. * @link http://php.net/manual/en/class.arrayiterator.php * + * @template TKey as array-key * @template TValue */ class ArrayIterator implements SeekableIterator, ArrayAccess, Serializable, Countable {