diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php index ab31e700a52..d498beff791 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php @@ -2,7 +2,9 @@ namespace Psalm\Internal\Analyzer\Statements\Expression\Call; +use AssertionError; use Psalm\Codebase; +use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\TypeExpander; use Psalm\Storage\ClassLikeStorage; use Psalm\Type; @@ -107,6 +109,23 @@ public static function collect( } } + $template_result = null; + if ($class_storage !== $static_class_storage && $static_class_storage->template_types) { + $templates = self::collect( + $codebase, + $static_class_storage, + $static_class_storage, + null, + $lhs_type_part + ); + if ($templates === null) { + throw new AssertionError("Could not collect templates!"); + } + $template_result = new TemplateResult( + $static_class_storage->template_types, + $templates + ); + } foreach ($template_types as $type_name => $_) { if (isset($class_template_params[$type_name])) { continue; @@ -118,9 +137,11 @@ public static function collect( $input_type_extends = $e[$class_storage->name][$type_name]; $output_type_extends = self::resolveTemplateParam( + $codebase, $input_type_extends, $static_class_storage, - $lhs_type_part + $lhs_type_part, + $template_result ); $class_template_params[$type_name][$class_storage->name] @@ -163,10 +184,12 @@ public static function collect( return $class_template_params; } - public static function resolveTemplateParam( + private static function resolveTemplateParam( + Codebase $codebase, Union $input_type_extends, ClassLikeStorage $static_class_storage, - TGenericObject $lhs_type_part + TGenericObject $lhs_type_part, + ?TemplateResult $template_result = null ): ?Union { $output_type_extends = null; foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) { @@ -199,12 +222,14 @@ public static function resolveTemplateParam( [$type_extends_atomic->param_name] )) { $nested_output_type = self::resolveTemplateParam( + $codebase, $static_class_storage ->template_extended_params [$type_extends_atomic->defining_class] [$type_extends_atomic->param_name], $static_class_storage, - $lhs_type_part + $lhs_type_part, + $template_result ); if ($nested_output_type !== null) { $output_type_extends = Type::combineUnionTypes( @@ -214,6 +239,13 @@ public static function resolveTemplateParam( } } } else { + if ($template_result !== null) { + $type_extends_atomic = clone $type_extends_atomic; + $type_extends_atomic->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase + ); + } $output_type_extends = Type::combineUnionTypes( new Union([$type_extends_atomic]), $output_type_extends diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index ef1cda7ba81..91c733e18a4 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -3764,6 +3764,40 @@ protected function setUp(): void } }', ], + 'complexTypes' => [ + 'code' => 'v; } + } + + + /** + * @template TTObject + * + * @extends Future> + */ + class FutureB extends Future { + /** @param TTObject $data */ + public function __construct($data) { parent::__construct(new ArrayObject([$data])); } + } + + $a = new FutureB(123); + + $r = $a->get();', + 'assertions' => [ + '$a===' => 'FutureB<123>', + '$r===' => 'ArrayObject' + ] + ] ]; }