Skip to content

Commit

Permalink
Merge pull request #7385 from b2pweb/handle-template-on-pseudo-method
Browse files Browse the repository at this point in the history
Add support of template for docblock methods
  • Loading branch information
orklah committed Jan 15, 2022
2 parents 75947c9 + fe7665a commit 9a9243e
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 8 deletions.
Expand Up @@ -354,7 +354,8 @@ public static function analyze(
$context,
$codebase->config,
$all_intersection_return_type,
$result
$result,
$lhs_type_part
);

if ($new_call_context) {
Expand Down Expand Up @@ -417,7 +418,8 @@ public static function analyze(
$all_intersection_existent_method_ids,
$intersection_method_id,
$cased_method_id,
$result
$result,
$lhs_type_part
);

return;
Expand Down
Expand Up @@ -8,9 +8,12 @@
use Psalm\Config;
use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Node\Expr\VirtualArray;
use Psalm\Node\Expr\VirtualArrayItem;
Expand All @@ -19,6 +22,7 @@
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Union;

Expand All @@ -36,7 +40,8 @@ public static function handleMagicMethod(
Context $context,
Config $config,
?Union $all_intersection_return_type,
AtomicMethodCallAnalysisResult $result
AtomicMethodCallAnalysisResult $result,
?Atomic $lhs_type_part
): ?AtomicCallContext {
$fq_class_name = $method_id->fq_class_name;
$method_name_lc = $method_id->method_name;
Expand Down Expand Up @@ -106,13 +111,23 @@ public static function handleMagicMethod(

[$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage;

$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
!$statements_analyzer->isStatic() && $method_id->fq_class_name === $context->self
);

ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
$pseudo_method_storage->params,
(string) $method_id,
true,
$context
$context,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null
);

ArgumentsAnalyzer::checkArgumentsMatch(
Expand All @@ -122,14 +137,22 @@ public static function handleMagicMethod(
$pseudo_method_storage->params,
$pseudo_method_storage,
null,
null,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null,
new CodeLocation($statements_analyzer, $stmt),
$context
);

if ($pseudo_method_storage->return_type) {
$return_type_candidate = clone $pseudo_method_storage->return_type;

if ($found_generic_params) {
TemplateInferredTypeReplacer::replace(
$return_type_candidate,
new TemplateResult([], $found_generic_params),
$codebase
);
}

$return_type_candidate = TypeExpander::expandUnion(
$codebase,
$return_type_candidate,
Expand Down Expand Up @@ -228,7 +251,8 @@ public static function handleMissingOrMagicMethod(
array $all_intersection_existent_method_ids,
?string $intersection_method_id,
string $cased_method_id,
AtomicMethodCallAnalysisResult $result
AtomicMethodCallAnalysisResult $result,
?Atomic $lhs_type_part
): void {
$fq_class_name = $method_id->fq_class_name;
$method_name_lc = $method_id->method_name;
Expand All @@ -254,13 +278,23 @@ public static function handleMissingOrMagicMethod(
return;
}

$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
!$statements_analyzer->isStatic() && $method_id->fq_class_name === $context->self
);

if (ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
$pseudo_method_storage->params,
(string) $method_id,
true,
$context
$context,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null
) === false) {
return;
}
Expand All @@ -272,7 +306,7 @@ public static function handleMissingOrMagicMethod(
$pseudo_method_storage->params,
$pseudo_method_storage,
null,
null,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null,
new CodeLocation($statements_analyzer, $stmt->name),
$context
) === false) {
Expand All @@ -282,6 +316,14 @@ public static function handleMissingOrMagicMethod(
if ($pseudo_method_storage->return_type) {
$return_type_candidate = clone $pseudo_method_storage->return_type;

if ($found_generic_params) {
TemplateInferredTypeReplacer::replace(
$return_type_candidate,
new TemplateResult([], $found_generic_params),
$codebase
);
}

if ($all_intersection_return_type) {
$return_type_candidate = Type::intersectUnionTypes(
$all_intersection_return_type,
Expand Down
60 changes: 60 additions & 0 deletions tests/Template/ClassTemplateTest.php
Expand Up @@ -3580,6 +3580,46 @@ function bar(A $a, B $b): void {
foo($a, $b);
}'
],
'templateOnDocblockMethod' => [
'<?php
/**
* @template T
* @method T get()
* @method void set(T $value)
*/
class Container
{
public function __call(string $name, array $args) {}
}
class A {}
function foo(A $a): void {}
/** @var Container<A> $container */
$container = new Container();
$container->set(new A());
foo($container->get());
'
],
'templateOnDocblockMethodOnInterface' => [
'<?php
/**
* @template T
* @method T get()
* @method void set(T $value)
*/
interface Container
{
}
class A {}
function foo(A $a): void {}
/** @var Container<A> $container */
$container->set(new A());
foo($container->get());
'
],
];
}

Expand Down Expand Up @@ -4340,6 +4380,26 @@ function foo(Collection $c, object $d): void {
}',
'error_message' => 'InvalidArgument',
],
'invalidTemplateArgumentOnDocblockMethod' => [
'<?php
/**
* @template T
* @method void set(T $value)
*/
class Container
{
public function __call(string $name, array $args) {}
}
class A {}
class B {}
/** @var Container<A> $container */
$container = new Container();
$container->set(new B());
',
'error_message' => 'InvalidArgument',
],
];
}
}

0 comments on commit 9a9243e

Please sign in to comment.