Skip to content
Permalink
Browse files

Fix #2346 - map covariance of templated return types when extending w…

…ith more params
  • Loading branch information
muglug committed Nov 14, 2019
1 parent 00be4c2 commit 0b1f83ff985bfc987b3846992c20626a35be58d5
Showing with 81 additions and 45 deletions.
  1. +54 −45 src/Psalm/Internal/Analyzer/TypeAnalyzer.php
  2. +27 −0 tests/Template/ClassTemplateExtendsTest.php
@@ -1832,60 +1832,71 @@ private static function isMatchingTypeContainedBy(
}
$input_type_params = $input_type_part->type_params;
$input_type_params_covariant = [];
if ($input_type_part->value !== $container_type_part->value) {
try {
$input_class_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
$template_extends = $input_class_storage->template_type_extends;
try {
$input_class_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
$input_type_params_covariant = $input_class_storage->template_covariants;
} catch (\Throwable $e) {
$input_class_storage = null;
}
if (isset($template_extends[$container_type_part->value])) {
$params = $template_extends[$container_type_part->value];
if ($input_type_part->value !== $container_type_part->value && $input_class_storage) {
$template_extends = $input_class_storage->template_type_extends;
$new_input_params = [];
if (isset($template_extends[$container_type_part->value])) {
$params = $template_extends[$container_type_part->value];
foreach ($params as $key => $extended_input_param_type) {
if (is_string($key)) {
$new_input_param = null;
foreach ($extended_input_param_type->getTypes() as $et) {
if ($et instanceof TTemplateParam
&& $et->param_name
&& isset($input_class_storage->template_types[$et->param_name])
) {
$old_params_offset = (int) array_search(
$et->param_name,
array_keys($input_class_storage->template_types)
);
if (!isset($input_type_params[$old_params_offset])) {
return false;
}
$candidate_param_type = $input_type_params[$old_params_offset];
} else {
$candidate_param_type = new Type\Union([$et]);
}
$new_input_params = [];
$new_params_covariant = [];
$candidate_param_type->from_template_default = true;
foreach ($params as $key => $extended_input_param_type) {
if (is_string($key)) {
$new_input_param = null;
if (!$new_input_param) {
$new_input_param = $candidate_param_type;
} else {
$new_input_param = Type::combineUnionTypes(
$new_input_param,
$candidate_param_type
);
foreach ($extended_input_param_type->getTypes() as $et) {
if ($et instanceof TTemplateParam
&& $et->param_name
&& isset($input_class_storage->template_types[$et->param_name])
) {
$old_params_offset = (int) array_search(
$et->param_name,
array_keys($input_class_storage->template_types)
);
if (!isset($input_type_params[$old_params_offset])) {
return false;
}
$new_params_covariant[count($new_input_params)]
= $input_type_params_covariant[$old_params_offset] ?? false;
$candidate_param_type = $input_type_params[$old_params_offset];
} else {
$offset = count($new_input_params);
$new_params_covariant[$offset]
= $input_type_params_covariant[$offset] ?? false;
$candidate_param_type = new Type\Union([$et]);
}
$new_input_params[] = $new_input_param ?: Type::getMixed();
$candidate_param_type->from_template_default = true;
if (!$new_input_param) {
$new_input_param = $candidate_param_type;
} else {
$new_input_param = Type::combineUnionTypes(
$new_input_param,
$candidate_param_type
);
}
}
}
$input_type_params = $new_input_params;
$new_input_params[] = $new_input_param ?: Type::getMixed();
}
}
} catch (\Throwable $t) {
// do nothing
$input_type_params = $new_input_params;
$input_type_params_covariant = $new_params_covariant;
}
}
@@ -1959,9 +1970,7 @@ private static function isMatchingTypeContainedBy(
= clone $container_param;
}
} else {
$input_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
if (!($input_storage->template_covariants[$i] ?? false)) {
if ($input_class_storage && !($input_type_params_covariant[$i] ?? false)) {
// Make sure types are basically the same
if (!self::isContainedBy(
$codebase,
@@ -2519,6 +2519,33 @@ function repository(): Maybe {
$anInt = $maybe->extract();
}'
],
'allowExtendingInterfaceWithExtraParam' => [
'<?php
usesElementInterfaceCollection(new Collection([ new Element ]));
/**
* @template TKey as array-key
* @template-covariant TValue
*/
interface CollectionInterface {}
interface ElementInterface {}
/**
* @template-covariant T
* @template-implements CollectionInterface<int, T>
*/
class Collection implements CollectionInterface
{
/** @param list<T> $elements */
public function __construct(array $elements) {}
}
class Element implements ElementInterface {}
/** @param CollectionInterface<int, ElementInterface> $col */
function usesElementInterfaceCollection(CollectionInterface $col) :void {}'
],
];
}

0 comments on commit 0b1f83f

Please sign in to comment.
You can’t perform that action at this time.