diff --git a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php index 869a75cfe..9d98cd875 100644 --- a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php @@ -534,6 +534,21 @@ class MethodAnalyzer extends FunctionLikeAnalyzer $guide_classlike_storage->name ); + $guide_class_name_lc = strtolower($guide_classlike_storage->name); + + if (isset($implementer_classlike_storage->template_type_extends[$guide_class_name_lc])) { + $map = $implementer_classlike_storage->template_type_extends[$guide_class_name_lc]; + + $guide_method_storage_return_type->replaceTemplateTypesWithArgTypes( + array_map( + function (Type\Atomic $u) use ($guide_classlike_storage) : array { + return [new Type\Union([$u]), $guide_classlike_storage->name]; + }, + $map + ) + ); + } + // treat void as null when comparing against docblock implementer if ($implementer_method_storage_return_type->isVoid()) { $implementer_method_storage_return_type = Type::getNull(); diff --git a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php index 7c706ed3f..96883762f 100644 --- a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -1039,19 +1039,20 @@ class TypeAnalyzer $container_class_lc = strtolower($container_type_part->value); // attempt to transform it - if ($class_storage->template_value_extends - && isset($class_storage->template_value_extends[$container_class_lc]) - ) { - $extends_list = $class_storage->template_value_extends[$container_class_lc]; + if (isset($class_storage->template_type_extends[$container_class_lc])) { + $extends_list = $class_storage->template_type_extends[$container_class_lc]; + + $generic_params = []; + + foreach ($extends_list as $key => $value) { + if (is_int($key)) { + $generic_params[] = new Type\Union([$value]); + } + } $input_type_part = new TGenericObject( $input_type_part->value, - array_map( - function (Type\Atomic $a) : Type\Union { - return new Type\Union([$a]); - }, - $extends_list - ) + $generic_params ); } } diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index d634f9681..e5dab070b 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -333,20 +333,17 @@ class Populator $storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes); - if (isset($storage->template_type_extends[$parent_storage_class])) { - $i = 0; + if ($parent_storage->template_types + && isset($storage->template_type_extends[$parent_storage_class]) + ) { + foreach ($storage->template_type_extends[$parent_storage_class] as $i => $type) { + $parent_template_type_names = array_keys($parent_storage->template_types); - foreach ($storage->template_type_extends[$parent_storage_class] as $template_name => $_) { - if ($parent_storage->template_types) { - $parent_template_type_names = array_keys($parent_storage->template_types); + $mapped_name = $parent_template_type_names[$i] ?? null; - if (isset($parent_template_type_names[$i])) { - $storage->template_type_extends[$parent_storage_class][$template_name] - = $parent_template_type_names[$i]; - } + if ($mapped_name) { + $storage->template_type_extends[$parent_storage_class][$mapped_name] = $type; } - - $i++; } } @@ -479,20 +476,17 @@ class Populator $implemented_interface_storage->invalid_dependencies ); - if (isset($storage->template_type_extends[$implemented_interface_lc])) { - $i = 0; + if ($implemented_interface_storage->template_types + && isset($storage->template_type_extends[$implemented_interface_lc]) + ) { + foreach ($storage->template_type_extends[$implemented_interface_lc] as $i => $type) { + $parent_template_type_names = array_keys($implemented_interface_storage->template_types); - if ($implemented_interface_storage->template_types) { - foreach ($storage->template_type_extends[$implemented_interface_lc] as $template_name => $_) { - $parent_template_type_names = array_keys($implemented_interface_storage->template_types); + $mapped_name = $parent_template_type_names[$i] ?? null; - if (isset($parent_template_type_names[$i])) { - $storage->template_type_extends[$implemented_interface_lc][$template_name] - = $parent_template_type_names[$i]; - } + if ($mapped_name) { + $storage->template_type_extends[$implemented_interface_lc][$mapped_name] = $type; } - - $i++; } } diff --git a/src/Psalm/Internal/Visitor/ReflectorVisitor.php b/src/Psalm/Internal/Visitor/ReflectorVisitor.php index c3b34de94..2f0a4be5b 100644 --- a/src/Psalm/Internal/Visitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/Visitor/ReflectorVisitor.php @@ -885,7 +885,6 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse } $extended_type_parameters = []; - $extended_type_values = []; foreach ($atomic_type->type_params as $i => $type_param) { if (!$type_param->isSingle()) { @@ -900,21 +899,13 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse } foreach ($type_param->getTypes() as $type_param_atomic) { - if ($type_param_atomic instanceof Type\Atomic\TGenericParam) { - $extended_type_parameters[$type_param_atomic->param_name] = null; - } else { - $extended_type_values[$i] = $type_param_atomic; - } + $extended_type_parameters[] = $type_param_atomic; } } if ($extended_type_parameters) { $storage->template_type_extends[$generic_class_lc] = $extended_type_parameters; } - - if ($extended_type_values) { - $storage->template_value_extends[$generic_class_lc] = $extended_type_values; - } } } diff --git a/src/Psalm/Storage/ClassLikeStorage.php b/src/Psalm/Storage/ClassLikeStorage.php index 07e1e0964..600fcf729 100644 --- a/src/Psalm/Storage/ClassLikeStorage.php +++ b/src/Psalm/Storage/ClassLikeStorage.php @@ -266,15 +266,10 @@ class ClassLikeStorage public $template_types; /** - * @var array>|null + * @var array>|null */ public $template_type_extends; - /** - * @var array>|null - */ - public $template_value_extends; - /** * @var array>|null */ diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index c0d052411..727b17baa 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -11,6 +11,7 @@ use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TLiteralFloat; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; +use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TString; use Psalm\Internal\Type\TypeCombination; @@ -876,22 +877,27 @@ class Union $classlike_storage = $codebase->classlike_storage_provider->get($atomic_input_type->value); - if (isset($classlike_storage->template_type_extends[strtolower($key)])) { + if ($atomic_input_type instanceof TGenericObject + && isset($classlike_storage->template_type_extends[strtolower($key)]) + ) { $matching_atomic_type = $atomic_input_type; break; } - if (isset($classlike_storage->template_value_extends[strtolower($key)])) { - $extends_list = $classlike_storage->template_value_extends[strtolower($key)]; + if (isset($classlike_storage->template_type_extends[strtolower($key)])) { + $extends_list = $classlike_storage->template_type_extends[strtolower($key)]; - $matching_atomic_type = new Type\Atomic\TGenericObject( + $generic_params = []; + + foreach ($extends_list as $key => $value) { + if (is_int($key)) { + $generic_params[] = new Type\Union([$value]); + } + } + + $matching_atomic_type = new TGenericObject( $atomic_input_type->value, - array_map( - function (Type\Atomic $a) : Type\Union { - return new Type\Union([$a]); - }, - $extends_list - ) + $generic_params ); break; } @@ -955,12 +961,11 @@ class Union if ($classlike_storage->template_type_extends) { foreach ($classlike_storage->template_type_extends as $fq_class_name_lc => $param_map) { - $param_map_reversed = array_flip($param_map); if (strtolower($atomic_type->defining_class) === $fq_class_name_lc - && isset($param_map_reversed[$key]) - && isset($template_types[$param_map_reversed[$key]]) + && isset($param_map[$key]) + && isset($template_types[(string) $param_map[$key]]) ) { - $template_type = clone $template_types[$param_map_reversed[$key]][0]; + $template_type = clone $template_types[(string) $param_map[$key]][0]; } } } diff --git a/tests/TemplateTest.php b/tests/TemplateTest.php index 38908475d..5678e93d6 100644 --- a/tests/TemplateTest.php +++ b/tests/TemplateTest.php @@ -1878,6 +1878,44 @@ class TemplateTest extends TestCase apply(function(AChild $_i) : void {}, new A());', 'error_message' => 'TypeCoercion', ], + 'extendsWithUnfulfilledNonTemplate' => [ + ' + */ + class BarContainer extends Container + { + /** + * @return Foo + */ + public function getItem() + { + return new Foo(); + } + }', + 'error_message' => 'ImplementedReturnTypeMismatch', + ], ]; } }