diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 74fcc847621..15bc9f7cced 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -964,6 +964,15 @@ private static function compareMethodDocblockReturnTypes( $guide_method_storage_return_type, $codebase ); + + if ($implementer_method_storage->defining_fqcln) { + self::transformTemplates( + $implementer_classlike_storage->template_extended_params, + $implementer_method_storage->defining_fqcln, + $implementer_method_storage_return_type, + $codebase + ); + } } if ($implementer_classlike_storage->is_trait) { diff --git a/tests/ReturnTypeTest.php b/tests/ReturnTypeTest.php index 2198e88e8fe..e901d595d57 100644 --- a/tests/ReturnTypeTest.php +++ b/tests/ReturnTypeTest.php @@ -1002,6 +1002,37 @@ function a($end): void{ } }' ], + 'returnTypeOfAbstractAndConcreteMethodFromTemplatedTraits' => [ + 'value; + } + } + + /** @template T */ + trait AnotherTrait { + /** @psalm-return T */ + abstract public function getValue(); + } + + class Test { + /** @use AggregateTrait */ + use AggregateTrait; + + /** @use AnotherTrait */ + use AnotherTrait; + + public function __construct() { + $this->value = 123; + } + }' + ] ]; } @@ -1435,6 +1466,39 @@ function f(): object { ', 'error_message' => 'LessSpecificReturnStatement', ], + 'lessSpecificImplementedReturnTypeFromTemplatedTraitMethod' => [ + 'value; + } + } + + /** @template T */ + trait AnotherTrait { + /** @psalm-return T */ + abstract public function getValue(); + } + + /** @template T */ + class Test { + /** @use AggregateTrait */ + use AggregateTrait; + + /** @use AnotherTrait */ + use AnotherTrait; + + public function __construct() { + $this->value = 123; + } + }', + 'error_message' => 'LessSpecificImplementedReturnType', + ] ]; } }