From a3df6505f0bd58b812c4c40109361cb0bd4e22d4 Mon Sep 17 00:00:00 2001 From: klimick Date: Mon, 9 Oct 2023 16:49:38 +0300 Subject: [PATCH] Type check nested templates --- .../Internal/TypeVisitor/TypeChecker.php | 21 +++++++-- tests/CallableTest.php | 34 ++++++++++++++ tests/Template/FunctionTemplateTest.php | 47 +++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/TypeVisitor/TypeChecker.php b/src/Psalm/Internal/TypeVisitor/TypeChecker.php index 4c2e52c916b..2818b414c16 100644 --- a/src/Psalm/Internal/TypeVisitor/TypeChecker.php +++ b/src/Psalm/Internal/TypeVisitor/TypeChecker.php @@ -9,6 +9,9 @@ use Psalm\Internal\Analyzer\ClassLikeNameOptions; use Psalm\Internal\Analyzer\MethodAnalyzer; use Psalm\Internal\Type\Comparator\UnionTypeComparator; +use Psalm\Internal\Type\TemplateBound; +use Psalm\Internal\Type\TemplateInferredTypeReplacer; +use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\TypeExpander; use Psalm\Issue\DeprecatedClass; use Psalm\Issue\DeprecatedInterface; @@ -241,6 +244,7 @@ private function checkGenericParams(TGenericObject $atomic): void } $expected_type_param_keys = array_keys($expected_type_params); + $template_result = new TemplateResult($expected_type_params, []); foreach ($atomic->type_params as $i => $type_param) { $this->prevent_template_covariance = $this->source instanceof MethodAnalyzer @@ -251,12 +255,16 @@ private function checkGenericParams(TGenericObject $atomic): void $expected_template_name = $expected_type_param_keys[$i]; foreach ($expected_type_params[$expected_template_name] as $defining_class => $expected_type_param) { - $expected_type_param = TypeExpander::expandUnion( + $expected_type_param = TemplateInferredTypeReplacer::replace( + TypeExpander::expandUnion( + $codebase, + $expected_type_param, + $defining_class, + null, + null, + ), + $template_result, $codebase, - $expected_type_param, - $defining_class, - null, - null, ); $type_param = TypeExpander::expandUnion( @@ -279,6 +287,9 @@ private function checkGenericParams(TGenericObject $atomic): void ), $this->suppressed_issues, ); + } else { + $template_result->lower_bounds[$expected_template_name][$defining_class][] + = new TemplateBound($type_param); } } } diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 5815222b1ad..605213b4a51 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1844,6 +1844,40 @@ function int_int_int_int(Closure $f): void {} 'ignored_issues' => [], 'php_version' => '8.0', ], + 'inferTypeWithNestedTemplatesAndExplicitTypeHint' => [ + 'code' => '> + */ + final class GetListOfNumbers implements Message {} + + /** + * @template TResult + * @template TMessage of Message + */ + final class Envelope {} + + /** + * @template TResult + * @template TMessage of Message + * @param class-string $_message + * @param callable(TMessage, Envelope): TResult $_handler + */ + function addHandler(string $_message, callable $_handler): void {} + + addHandler(GetListOfNumbers::class, function (Message $_message, Envelope $_envelope) { + /** + * @psalm-check-type-exact $_message = GetListOfNumbers + * @psalm-check-type-exact $_envelope = Envelope, GetListOfNumbers> + */ + return [1, 2, 3]; + });', + ], ]; } diff --git a/tests/Template/FunctionTemplateTest.php b/tests/Template/FunctionTemplateTest.php index ba72c2604d2..800e2956cd7 100644 --- a/tests/Template/FunctionTemplateTest.php +++ b/tests/Template/FunctionTemplateTest.php @@ -1673,6 +1673,29 @@ function foo(string $t): string return $t; }', ], + 'typeWithNestedTemplates' => [ + 'code' => ' + */ + final class BType {} + + /** + * @param BType> $_value + */ + function test1(BType $_value): void {} + + /** + * @param BType> $_value + */ + function test2(BType $_value): void {}', + ], ]; } @@ -2268,6 +2291,30 @@ function jsonFromEntityCollection(Container $c): void { }', 'error_message' => 'InvalidArgument', ], + 'catchInvalidTemplateTypeWithNestedTemplates' => [ + 'code' => ' + */ + final class BType {} + + /** + * @param BType> $_value + */ + function test1(BType $_value): void {} + + /** + * @param BType> $_value + */ + function test2(BType $_value): void {}', + 'error_message' => 'InvalidTemplateParam', + ], ]; } }