Skip to content

Commit

Permalink
Merge pull request #10271 from klimick/type-check-nested-templates
Browse files Browse the repository at this point in the history
Type check nested templates
  • Loading branch information
orklah authored Oct 9, 2023
2 parents 3f7306d + a3df650 commit ee4e8aa
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 5 deletions.
21 changes: 16 additions & 5 deletions src/Psalm/Internal/TypeVisitor/TypeChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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);
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions tests/CallableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1844,6 +1844,40 @@ function int_int_int_int(Closure $f): void {}
'ignored_issues' => [],
'php_version' => '8.0',
],
'inferTypeWithNestedTemplatesAndExplicitTypeHint' => [
'code' => '<?php
/**
* @template TResult
*/
interface Message {}
/**
* @implements Message<list<int>>
*/
final class GetListOfNumbers implements Message {}
/**
* @template TResult
* @template TMessage of Message<TResult>
*/
final class Envelope {}
/**
* @template TResult
* @template TMessage of Message<TResult>
* @param class-string<TMessage> $_message
* @param callable(TMessage, Envelope<TResult, TMessage>): 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<list<int>, GetListOfNumbers>
*/
return [1, 2, 3];
});',
],
];
}

Expand Down
47 changes: 47 additions & 0 deletions tests/Template/FunctionTemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,29 @@ function foo(string $t): string
return $t;
}',
],
'typeWithNestedTemplates' => [
'code' => '<?php
/**
* @template T of object
*/
interface AType {}
/**
* @template T of object
* @template B of AType<T>
*/
final class BType {}
/**
* @param BType<object, AType<object>> $_value
*/
function test1(BType $_value): void {}
/**
* @param BType<stdClass, AType<stdClass>> $_value
*/
function test2(BType $_value): void {}',
],
];
}

Expand Down Expand Up @@ -2268,6 +2291,30 @@ function jsonFromEntityCollection(Container $c): void {
}',
'error_message' => 'InvalidArgument',
],
'catchInvalidTemplateTypeWithNestedTemplates' => [
'code' => '<?php
/**
* @template T
*/
interface AType {}
/**
* @template T
* @template B of AType<T>
*/
final class BType {}
/**
* @param BType<string, AType<int>> $_value
*/
function test1(BType $_value): void {}
/**
* @param BType<int, AType<string>> $_value
*/
function test2(BType $_value): void {}',
'error_message' => 'InvalidTemplateParam',
],
];
}
}

0 comments on commit ee4e8aa

Please sign in to comment.